Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
- Thank you for contacting us. We will respond to you as soon as possible.
-
-
-
- Note that if you turn on the Yii debugger, you should be able
- to view the mail message on the mail panel of the debugger.
- mail->useFileTransport): ?>
- Because the application is in development mode, the email is not sent but saved as
- a file under = Yii::getAlias(Yii::$app->mail->fileTransportPath) ?>.
- Please configure the useFileTransport property of the mail
- application component to be false to enable email sending.
-
-
-
-
-
-
- If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
-
+ Thank you for contacting us. We will respond to you as soon as possible.
+
+
+
+ Note that if you turn on the Yii debugger, you should be able
+ to view the mail message on the mail panel of the debugger.
+ mail->useFileTransport): ?>
+ Because the application is in development mode, the email is not sent but saved as
+ a file under = Yii::getAlias(Yii::$app->mail->fileTransportPath) ?>.
+ Please configure the useFileTransport property of the mail
+ application component to be false to enable email sending.
+
+
+
+
+
+
+ If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
diff --git a/extensions/authclient/widgets/Choice.php b/extensions/authclient/widgets/Choice.php
index 0d5d7625961..d5622a74833 100644
--- a/extensions/authclient/widgets/Choice.php
+++ b/extensions/authclient/widgets/Choice.php
@@ -60,182 +60,187 @@
*/
class Choice extends Widget
{
- /**
- * @var string name of the auth client collection application component.
- * This component will be used to fetch services value if it is not set.
- */
- public $clientCollection = 'authClientCollection';
- /**
- * @var string name of the GET param , which should be used to passed auth client id to URL
- * defined by [[baseAuthUrl]].
- */
- public $clientIdGetParamName = 'authclient';
- /**
- * @var array the HTML attributes that should be rendered in the div HTML tag representing the container element.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [
- 'class' => 'auth-clients'
- ];
- /**
- * @var boolean indicates if popup window should be used instead of direct links.
- */
- public $popupMode = true;
- /**
- * @var boolean indicates if widget content, should be rendered automatically.
- * Note: this value automatically set to 'false' at the first call of [[createProviderUrl()]]
- */
- public $autoRender = true;
-
- /**
- * @var array configuration for the external clients base authentication URL.
- */
- private $_baseAuthUrl;
- /**
- * @var ClientInterface[] auth providers list.
- */
- private $_clients;
-
- /**
- * @param ClientInterface[] $clients auth providers
- */
- public function setClients(array $clients)
- {
- $this->_clients = $clients;
- }
-
- /**
- * @return ClientInterface[] auth providers
- */
- public function getClients()
- {
- if ($this->_clients === null) {
- $this->_clients = $this->defaultClients();
- }
- return $this->_clients;
- }
-
- /**
- * @param array $baseAuthUrl base auth URL configuration.
- */
- public function setBaseAuthUrl(array $baseAuthUrl)
- {
- $this->_baseAuthUrl = $baseAuthUrl;
- }
-
- /**
- * @return array base auth URL configuration.
- */
- public function getBaseAuthUrl()
- {
- if (!is_array($this->_baseAuthUrl)) {
- $this->_baseAuthUrl = $this->defaultBaseAuthUrl();
- }
- return $this->_baseAuthUrl;
- }
-
- /**
- * Returns default auth clients list.
- * @return ClientInterface[] auth clients list.
- */
- protected function defaultClients()
- {
- /** @var $collection \yii\authclient\Collection */
- $collection = Yii::$app->getComponent($this->clientCollection);
- return $collection->getClients();
- }
-
- /**
- * Composes default base auth URL configuration.
- * @return array base auth URL configuration.
- */
- protected function defaultBaseAuthUrl()
- {
- $baseAuthUrl = [
- Yii::$app->controller->getRoute()
- ];
- $params = $_GET;
- unset($params[$this->clientIdGetParamName]);
- $baseAuthUrl = array_merge($baseAuthUrl, $params);
- return $baseAuthUrl;
- }
-
- /**
- * Outputs client auth link.
- * @param ClientInterface $client external auth client instance.
- * @param string $text link text, if not set - default value will be generated.
- * @param array $htmlOptions link HTML options.
- */
- public function clientLink($client, $text = null, array $htmlOptions = [])
- {
- if ($text === null) {
- $text = Html::tag('span', '', ['class' => 'auth-icon ' . $client->getName()]);
- $text .= Html::tag('span', $client->getTitle(), ['class' => 'auth-title']);
- }
- if (!array_key_exists('class', $htmlOptions)) {
- $htmlOptions['class'] = 'auth-link ' . $client->getName();
- }
- if ($this->popupMode) {
- $viewOptions = $client->getViewOptions();
- if (isset($viewOptions['popupWidth'])) {
- $htmlOptions['data-popup-width'] = $viewOptions['popupWidth'];
- }
- if (isset($viewOptions['popupHeight'])) {
- $htmlOptions['data-popup-height'] = $viewOptions['popupHeight'];
- }
- }
- echo Html::a($text, $this->createClientUrl($client), $htmlOptions);
- }
-
- /**
- * Composes client auth URL.
- * @param ClientInterface $provider external auth client instance.
- * @return string auth URL.
- */
- public function createClientUrl($provider)
- {
- $this->autoRender = false;
- $url = $this->getBaseAuthUrl();
- $url[$this->clientIdGetParamName] = $provider->getId();
- return Url::to($url);
- }
-
- /**
- * Renders the main content, which includes all external services links.
- */
- protected function renderMainContent()
- {
- echo Html::beginTag('ul', ['class' => 'auth-clients clear']);
- foreach ($this->getClients() as $externalService) {
- echo Html::beginTag('li', ['class' => 'auth-client']);
- $this->clientLink($externalService);
- echo Html::endTag('li');
- }
- echo Html::endTag('ul');
- }
-
- /**
- * Initializes the widget.
- */
- public function init()
- {
- if ($this->popupMode) {
- $view = Yii::$app->getView();
- ChoiceAsset::register($view);
- $view->registerJs("\$('#" . $this->getId() . "').authchoice();");
- }
- $this->options['id'] = $this->getId();
- echo Html::beginTag('div', $this->options);
- }
-
- /**
- * Runs the widget.
- */
- public function run()
- {
- if ($this->autoRender) {
- $this->renderMainContent();
- }
- echo Html::endTag('div');
- }
+ /**
+ * @var string name of the auth client collection application component.
+ * This component will be used to fetch services value if it is not set.
+ */
+ public $clientCollection = 'authClientCollection';
+ /**
+ * @var string name of the GET param , which should be used to passed auth client id to URL
+ * defined by [[baseAuthUrl]].
+ */
+ public $clientIdGetParamName = 'authclient';
+ /**
+ * @var array the HTML attributes that should be rendered in the div HTML tag representing the container element.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [
+ 'class' => 'auth-clients'
+ ];
+ /**
+ * @var boolean indicates if popup window should be used instead of direct links.
+ */
+ public $popupMode = true;
+ /**
+ * @var boolean indicates if widget content, should be rendered automatically.
+ * Note: this value automatically set to 'false' at the first call of [[createProviderUrl()]]
+ */
+ public $autoRender = true;
+
+ /**
+ * @var array configuration for the external clients base authentication URL.
+ */
+ private $_baseAuthUrl;
+ /**
+ * @var ClientInterface[] auth providers list.
+ */
+ private $_clients;
+
+ /**
+ * @param ClientInterface[] $clients auth providers
+ */
+ public function setClients(array $clients)
+ {
+ $this->_clients = $clients;
+ }
+
+ /**
+ * @return ClientInterface[] auth providers
+ */
+ public function getClients()
+ {
+ if ($this->_clients === null) {
+ $this->_clients = $this->defaultClients();
+ }
+
+ return $this->_clients;
+ }
+
+ /**
+ * @param array $baseAuthUrl base auth URL configuration.
+ */
+ public function setBaseAuthUrl(array $baseAuthUrl)
+ {
+ $this->_baseAuthUrl = $baseAuthUrl;
+ }
+
+ /**
+ * @return array base auth URL configuration.
+ */
+ public function getBaseAuthUrl()
+ {
+ if (!is_array($this->_baseAuthUrl)) {
+ $this->_baseAuthUrl = $this->defaultBaseAuthUrl();
+ }
+
+ return $this->_baseAuthUrl;
+ }
+
+ /**
+ * Returns default auth clients list.
+ * @return ClientInterface[] auth clients list.
+ */
+ protected function defaultClients()
+ {
+ /** @var $collection \yii\authclient\Collection */
+ $collection = Yii::$app->getComponent($this->clientCollection);
+
+ return $collection->getClients();
+ }
+
+ /**
+ * Composes default base auth URL configuration.
+ * @return array base auth URL configuration.
+ */
+ protected function defaultBaseAuthUrl()
+ {
+ $baseAuthUrl = [
+ Yii::$app->controller->getRoute()
+ ];
+ $params = $_GET;
+ unset($params[$this->clientIdGetParamName]);
+ $baseAuthUrl = array_merge($baseAuthUrl, $params);
+
+ return $baseAuthUrl;
+ }
+
+ /**
+ * Outputs client auth link.
+ * @param ClientInterface $client external auth client instance.
+ * @param string $text link text, if not set - default value will be generated.
+ * @param array $htmlOptions link HTML options.
+ */
+ public function clientLink($client, $text = null, array $htmlOptions = [])
+ {
+ if ($text === null) {
+ $text = Html::tag('span', '', ['class' => 'auth-icon ' . $client->getName()]);
+ $text .= Html::tag('span', $client->getTitle(), ['class' => 'auth-title']);
+ }
+ if (!array_key_exists('class', $htmlOptions)) {
+ $htmlOptions['class'] = 'auth-link ' . $client->getName();
+ }
+ if ($this->popupMode) {
+ $viewOptions = $client->getViewOptions();
+ if (isset($viewOptions['popupWidth'])) {
+ $htmlOptions['data-popup-width'] = $viewOptions['popupWidth'];
+ }
+ if (isset($viewOptions['popupHeight'])) {
+ $htmlOptions['data-popup-height'] = $viewOptions['popupHeight'];
+ }
+ }
+ echo Html::a($text, $this->createClientUrl($client), $htmlOptions);
+ }
+
+ /**
+ * Composes client auth URL.
+ * @param ClientInterface $provider external auth client instance.
+ * @return string auth URL.
+ */
+ public function createClientUrl($provider)
+ {
+ $this->autoRender = false;
+ $url = $this->getBaseAuthUrl();
+ $url[$this->clientIdGetParamName] = $provider->getId();
+
+ return Url::to($url);
+ }
+
+ /**
+ * Renders the main content, which includes all external services links.
+ */
+ protected function renderMainContent()
+ {
+ echo Html::beginTag('ul', ['class' => 'auth-clients clear']);
+ foreach ($this->getClients() as $externalService) {
+ echo Html::beginTag('li', ['class' => 'auth-client']);
+ $this->clientLink($externalService);
+ echo Html::endTag('li');
+ }
+ echo Html::endTag('ul');
+ }
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ if ($this->popupMode) {
+ $view = Yii::$app->getView();
+ ChoiceAsset::register($view);
+ $view->registerJs("\$('#" . $this->getId() . "').authchoice();");
+ }
+ $this->options['id'] = $this->getId();
+ echo Html::beginTag('div', $this->options);
+ }
+
+ /**
+ * Runs the widget.
+ */
+ public function run()
+ {
+ if ($this->autoRender) {
+ $this->renderMainContent();
+ }
+ echo Html::endTag('div');
+ }
}
diff --git a/extensions/authclient/widgets/ChoiceAsset.php b/extensions/authclient/widgets/ChoiceAsset.php
index ecea8bd9014..f15ff7b8e02 100644
--- a/extensions/authclient/widgets/ChoiceAsset.php
+++ b/extensions/authclient/widgets/ChoiceAsset.php
@@ -17,14 +17,14 @@
*/
class ChoiceAsset extends AssetBundle
{
- public $sourcePath = '@yii/authclient/widgets/assets';
- public $js = [
- 'authchoice.js',
- ];
- public $css = [
- 'authchoice.css',
- ];
- public $depends = [
- 'yii\web\YiiAsset',
- ];
+ public $sourcePath = '@yii/authclient/widgets/assets';
+ public $js = [
+ 'authchoice.js',
+ ];
+ public $css = [
+ 'authchoice.css',
+ ];
+ public $depends = [
+ 'yii\web\YiiAsset',
+ ];
}
diff --git a/extensions/bootstrap/Alert.php b/extensions/bootstrap/Alert.php
index 15b33428acc..60c34c844d4 100644
--- a/extensions/bootstrap/Alert.php
+++ b/extensions/bootstrap/Alert.php
@@ -46,105 +46,105 @@
*/
class Alert extends Widget
{
- /**
- * @var string the body content in the alert component. Note that anything between
- * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated
- * as the body content, and will be rendered before this.
- */
- public $body;
- /**
- * @var array the options for rendering the close button tag.
- * The close button is displayed in the header of the modal window. Clicking
- * on the button will hide the modal window. If this is null, no close button will be rendered.
- *
- * The following special options are supported:
- *
- * - tag: string, the tag name of the button. Defaults to 'button'.
- * - label: string, the label of the button. Defaults to '×'.
- *
- * The rest of the options will be rendered as the HTML attributes of the button tag.
- * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts)
- * for the supported HTML attributes.
- */
- public $closeButton = [];
+ /**
+ * @var string the body content in the alert component. Note that anything between
+ * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated
+ * as the body content, and will be rendered before this.
+ */
+ public $body;
+ /**
+ * @var array the options for rendering the close button tag.
+ * The close button is displayed in the header of the modal window. Clicking
+ * on the button will hide the modal window. If this is null, no close button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to '×'.
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts)
+ * for the supported HTML attributes.
+ */
+ public $closeButton = [];
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
+ $this->initOptions();
- $this->initOptions();
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderBodyBegin() . "\n";
+ }
- echo Html::beginTag('div', $this->options) . "\n";
- echo $this->renderBodyBegin() . "\n";
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo "\n" . $this->renderBodyEnd();
+ echo "\n" . Html::endTag('div');
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo "\n" . $this->renderBodyEnd();
- echo "\n" . Html::endTag('div');
+ $this->registerPlugin('alert');
+ }
- $this->registerPlugin('alert');
- }
+ /**
+ * Renders the close button if any before rendering the content.
+ * @return string the rendering result
+ */
+ protected function renderBodyBegin()
+ {
+ return $this->renderCloseButton();
+ }
- /**
- * Renders the close button if any before rendering the content.
- * @return string the rendering result
- */
- protected function renderBodyBegin()
- {
- return $this->renderCloseButton();
- }
+ /**
+ * Renders the alert body (if any).
+ * @return string the rendering result
+ */
+ protected function renderBodyEnd()
+ {
+ return $this->body . "\n";
+ }
- /**
- * Renders the alert body (if any).
- * @return string the rendering result
- */
- protected function renderBodyEnd()
- {
- return $this->body . "\n";
- }
+ /**
+ * Renders the close button.
+ * @return string the rendering result
+ */
+ protected function renderCloseButton()
+ {
+ if ($this->closeButton !== null) {
+ $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
+ $label = ArrayHelper::remove($this->closeButton, 'label', '×');
+ if ($tag === 'button' && !isset($this->closeButton['type'])) {
+ $this->closeButton['type'] = 'button';
+ }
- /**
- * Renders the close button.
- * @return string the rendering result
- */
- protected function renderCloseButton()
- {
- if ($this->closeButton !== null) {
- $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
- $label = ArrayHelper::remove($this->closeButton, 'label', '×');
- if ($tag === 'button' && !isset($this->closeButton['type'])) {
- $this->closeButton['type'] = 'button';
- }
- return Html::tag($tag, $label, $this->closeButton);
- } else {
- return null;
- }
- }
+ return Html::tag($tag, $label, $this->closeButton);
+ } else {
+ return null;
+ }
+ }
- /**
- * Initializes the widget options.
- * This method sets the default values for various options.
- */
- protected function initOptions()
- {
- Html::addCssClass($this->options, 'alert');
- Html::addCssClass($this->options, 'fade');
- Html::addCssClass($this->options, 'in');
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions()
+ {
+ Html::addCssClass($this->options, 'alert');
+ Html::addCssClass($this->options, 'fade');
+ Html::addCssClass($this->options, 'in');
- if ($this->closeButton !== null) {
- $this->closeButton = array_merge([
- 'data-dismiss' => 'alert',
- 'aria-hidden' => 'true',
- 'class' => 'close',
- ], $this->closeButton);
- }
- }
+ if ($this->closeButton !== null) {
+ $this->closeButton = array_merge([
+ 'data-dismiss' => 'alert',
+ 'aria-hidden' => 'true',
+ 'class' => 'close',
+ ], $this->closeButton);
+ }
+ }
}
diff --git a/extensions/bootstrap/BootstrapAsset.php b/extensions/bootstrap/BootstrapAsset.php
index 93f7728e382..d5b12443b71 100644
--- a/extensions/bootstrap/BootstrapAsset.php
+++ b/extensions/bootstrap/BootstrapAsset.php
@@ -17,8 +17,8 @@
*/
class BootstrapAsset extends AssetBundle
{
- public $sourcePath = '@vendor/twbs/bootstrap/dist';
- public $css = [
- 'css/bootstrap.css',
- ];
+ public $sourcePath = '@vendor/twbs/bootstrap/dist';
+ public $css = [
+ 'css/bootstrap.css',
+ ];
}
diff --git a/extensions/bootstrap/BootstrapPluginAsset.php b/extensions/bootstrap/BootstrapPluginAsset.php
index c451ff4f434..13aa16217a6 100644
--- a/extensions/bootstrap/BootstrapPluginAsset.php
+++ b/extensions/bootstrap/BootstrapPluginAsset.php
@@ -17,12 +17,12 @@
*/
class BootstrapPluginAsset extends AssetBundle
{
- public $sourcePath = '@vendor/twbs/bootstrap/dist';
- public $js = [
- 'js/bootstrap.js',
- ];
- public $depends = [
- 'yii\web\JqueryAsset',
- 'yii\bootstrap\BootstrapAsset',
- ];
+ public $sourcePath = '@vendor/twbs/bootstrap/dist';
+ public $js = [
+ 'js/bootstrap.js',
+ ];
+ public $depends = [
+ 'yii\web\JqueryAsset',
+ 'yii\bootstrap\BootstrapAsset',
+ ];
}
diff --git a/extensions/bootstrap/BootstrapThemeAsset.php b/extensions/bootstrap/BootstrapThemeAsset.php
index fa424f934a7..093350cc990 100644
--- a/extensions/bootstrap/BootstrapThemeAsset.php
+++ b/extensions/bootstrap/BootstrapThemeAsset.php
@@ -17,11 +17,11 @@
*/
class BootstrapThemeAsset extends AssetBundle
{
- public $sourcePath = '@vendor/twbs/bootstrap/dist';
- public $css = [
- 'css/bootstrap-theme.css',
- ];
- public $depends = [
- 'yii\bootstrap\BootstrapAsset',
- ];
+ public $sourcePath = '@vendor/twbs/bootstrap/dist';
+ public $css = [
+ 'css/bootstrap-theme.css',
+ ];
+ public $depends = [
+ 'yii\bootstrap\BootstrapAsset',
+ ];
}
diff --git a/extensions/bootstrap/Button.php b/extensions/bootstrap/Button.php
index e1af48950dd..d13c9e9bfea 100644
--- a/extensions/bootstrap/Button.php
+++ b/extensions/bootstrap/Button.php
@@ -26,37 +26,36 @@
*/
class Button extends Widget
{
- /**
- * @var string the tag to use to render the button
- */
- public $tagName = 'button';
- /**
- * @var string the button label
- */
- public $label = 'Button';
- /**
- * @var boolean whether the label should be HTML-encoded.
- */
- public $encodeLabel = true;
+ /**
+ * @var string the tag to use to render the button
+ */
+ public $tagName = 'button';
+ /**
+ * @var string the button label
+ */
+ public $label = 'Button';
+ /**
+ * @var boolean whether the label should be HTML-encoded.
+ */
+ public $encodeLabel = true;
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->clientOptions = false;
+ Html::addCssClass($this->options, 'btn');
+ }
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- parent::init();
- $this->clientOptions = false;
- Html::addCssClass($this->options, 'btn');
- }
-
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options);
- $this->registerPlugin('button');
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options);
+ $this->registerPlugin('button');
+ }
}
diff --git a/extensions/bootstrap/ButtonDropdown.php b/extensions/bootstrap/ButtonDropdown.php
index 434f214e58c..dedf8db2676 100644
--- a/extensions/bootstrap/ButtonDropdown.php
+++ b/extensions/bootstrap/ButtonDropdown.php
@@ -33,91 +33,92 @@
*/
class ButtonDropdown extends Widget
{
- /**
- * @var string the button label
- */
- public $label = 'Button';
- /**
- * @var array the HTML attributes of the button.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array the configuration array for [[Dropdown]].
- */
- public $dropdown = [];
- /**
- * @var boolean whether to display a group of split-styled button group.
- */
- public $split = false;
- /**
- * @var string the tag to use to render the button
- */
- public $tagName = 'button';
- /**
- * @var boolean whether the label should be HTML-encoded.
- */
- public $encodeLabel = true;
+ /**
+ * @var string the button label
+ */
+ public $label = 'Button';
+ /**
+ * @var array the HTML attributes of the button.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array the configuration array for [[Dropdown]].
+ */
+ public $dropdown = [];
+ /**
+ * @var boolean whether to display a group of split-styled button group.
+ */
+ public $split = false;
+ /**
+ * @var string the tag to use to render the button
+ */
+ public $tagName = 'button';
+ /**
+ * @var boolean whether the label should be HTML-encoded.
+ */
+ public $encodeLabel = true;
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderButton() . "\n" . $this->renderDropdown();
+ $this->registerPlugin('button');
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo $this->renderButton() . "\n" . $this->renderDropdown();
- $this->registerPlugin('button');
- }
+ /**
+ * Generates the button dropdown.
+ * @return string the rendering result.
+ */
+ protected function renderButton()
+ {
+ Html::addCssClass($this->options, 'btn');
+ $label = $this->label;
+ if ($this->encodeLabel) {
+ $label = Html::encode($label);
+ }
+ if ($this->split) {
+ $options = $this->options;
+ $this->options['data-toggle'] = 'dropdown';
+ Html::addCssClass($this->options, 'dropdown-toggle');
+ $splitButton = Button::widget([
+ 'label' => '',
+ 'encodeLabel' => false,
+ 'options' => $this->options,
+ 'view' => $this->getView(),
+ ]);
+ } else {
+ $label .= ' ';
+ $options = $this->options;
+ if (!isset($options['href'])) {
+ $options['href'] = '#';
+ }
+ Html::addCssClass($options, 'dropdown-toggle');
+ $options['data-toggle'] = 'dropdown';
+ $splitButton = '';
+ }
- /**
- * Generates the button dropdown.
- * @return string the rendering result.
- */
- protected function renderButton()
- {
- Html::addCssClass($this->options, 'btn');
- $label = $this->label;
- if ($this->encodeLabel) {
- $label = Html::encode($label);
- }
- if ($this->split) {
- $options = $this->options;
- $this->options['data-toggle'] = 'dropdown';
- Html::addCssClass($this->options, 'dropdown-toggle');
- $splitButton = Button::widget([
- 'label' => '',
- 'encodeLabel' => false,
- 'options' => $this->options,
- 'view' => $this->getView(),
- ]);
- } else {
- $label .= ' ';
- $options = $this->options;
- if (!isset($options['href'])) {
- $options['href'] = '#';
- }
- Html::addCssClass($options, 'dropdown-toggle');
- $options['data-toggle'] = 'dropdown';
- $splitButton = '';
- }
- return Button::widget([
- 'tagName' => $this->tagName,
- 'label' => $label,
- 'options' => $options,
- 'encodeLabel' => false,
- 'view' => $this->getView(),
- ]) . "\n" . $splitButton;
- }
+ return Button::widget([
+ 'tagName' => $this->tagName,
+ 'label' => $label,
+ 'options' => $options,
+ 'encodeLabel' => false,
+ 'view' => $this->getView(),
+ ]) . "\n" . $splitButton;
+ }
- /**
- * Generates the dropdown menu.
- * @return string the rendering result.
- */
- protected function renderDropdown()
- {
- $config = $this->dropdown;
- $config['clientOptions'] = false;
- $config['view'] = $this->getView();
- return Dropdown::widget($config);
- }
+ /**
+ * Generates the dropdown menu.
+ * @return string the rendering result.
+ */
+ protected function renderDropdown()
+ {
+ $config = $this->dropdown;
+ $config['clientOptions'] = false;
+ $config['view'] = $this->getView();
+
+ return Dropdown::widget($config);
+ }
}
diff --git a/extensions/bootstrap/ButtonGroup.php b/extensions/bootstrap/ButtonGroup.php
index 66d2cc07bda..8b08b6103ac 100644
--- a/extensions/bootstrap/ButtonGroup.php
+++ b/extensions/bootstrap/ButtonGroup.php
@@ -39,60 +39,60 @@
*/
class ButtonGroup extends Widget
{
- /**
- * @var array list of buttons. Each array element represents a single button
- * which can be specified as a string or an array of the following structure:
- *
- * - label: string, required, the button label.
- * - options: array, optional, the HTML attributes of the button.
- */
- public $buttons = [];
- /**
- * @var boolean whether to HTML-encode the button labels.
- */
- public $encodeLabels = true;
+ /**
+ * @var array list of buttons. Each array element represents a single button
+ * which can be specified as a string or an array of the following structure:
+ *
+ * - label: string, required, the button label.
+ * - options: array, optional, the HTML attributes of the button.
+ */
+ public $buttons = [];
+ /**
+ * @var boolean whether to HTML-encode the button labels.
+ */
+ public $encodeLabels = true;
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'btn-group');
+ }
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- parent::init();
- Html::addCssClass($this->options, 'btn-group');
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::tag('div', $this->renderButtons(), $this->options);
+ BootstrapAsset::register($this->getView());
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::tag('div', $this->renderButtons(), $this->options);
- BootstrapAsset::register($this->getView());
- }
+ /**
+ * Generates the buttons that compound the group as specified on [[items]].
+ * @return string the rendering result.
+ */
+ protected function renderButtons()
+ {
+ $buttons = [];
+ foreach ($this->buttons as $button) {
+ if (is_array($button)) {
+ $label = ArrayHelper::getValue($button, 'label');
+ $options = ArrayHelper::getValue($button, 'options');
+ $buttons[] = Button::widget([
+ 'label' => $label,
+ 'options' => $options,
+ 'encodeLabel' => $this->encodeLabels,
+ 'view' => $this->getView()
+ ]);
+ } else {
+ $buttons[] = $button;
+ }
+ }
- /**
- * Generates the buttons that compound the group as specified on [[items]].
- * @return string the rendering result.
- */
- protected function renderButtons()
- {
- $buttons = [];
- foreach ($this->buttons as $button) {
- if (is_array($button)) {
- $label = ArrayHelper::getValue($button, 'label');
- $options = ArrayHelper::getValue($button, 'options');
- $buttons[] = Button::widget([
- 'label' => $label,
- 'options' => $options,
- 'encodeLabel' => $this->encodeLabels,
- 'view' => $this->getView()
- ]);
- } else {
- $buttons[] = $button;
- }
- }
- return implode("\n", $buttons);
- }
+ return implode("\n", $buttons);
+ }
}
diff --git a/extensions/bootstrap/Carousel.php b/extensions/bootstrap/Carousel.php
index eaeb6a5669b..40291a21275 100644
--- a/extensions/bootstrap/Carousel.php
+++ b/extensions/bootstrap/Carousel.php
@@ -39,132 +39,133 @@
*/
class Carousel extends Widget
{
- /**
- * @var array|boolean the labels for the previous and the next control buttons.
- * If false, it means the previous and the next control buttons should not be displayed.
- */
- public $controls = ['‹', '›'];
- /**
- * @var array list of slides in the carousel. Each array element represents a single
- * slide with the following structure:
- *
- * ```php
- * [
- * // required, slide content (HTML), such as an image tag
- * 'content' => '',
- * // optional, the caption (HTML) of the slide
- * 'caption' => '
This is title
This is the caption text
',
- * // optional the HTML attributes of the slide container
- * 'options' => [],
- * ]
- * ```
- */
- public $items = [];
+ /**
+ * @var array|boolean the labels for the previous and the next control buttons.
+ * If false, it means the previous and the next control buttons should not be displayed.
+ */
+ public $controls = ['‹', '›'];
+ /**
+ * @var array list of slides in the carousel. Each array element represents a single
+ * slide with the following structure:
+ *
+ * ```php
+ * [
+ * // required, slide content (HTML), such as an image tag
+ * 'content' => '',
+ * // optional, the caption (HTML) of the slide
+ * 'caption' => '
This is title
This is the caption text
',
+ * // optional the HTML attributes of the slide container
+ * 'options' => [],
+ * ]
+ * ```
+ */
+ public $items = [];
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'carousel');
+ }
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- Html::addCssClass($this->options, 'carousel');
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderIndicators() . "\n";
+ echo $this->renderItems() . "\n";
+ echo $this->renderControls() . "\n";
+ echo Html::endTag('div') . "\n";
+ $this->registerPlugin('carousel');
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::beginTag('div', $this->options) . "\n";
- echo $this->renderIndicators() . "\n";
- echo $this->renderItems() . "\n";
- echo $this->renderControls() . "\n";
- echo Html::endTag('div') . "\n";
- $this->registerPlugin('carousel');
- }
+ /**
+ * Renders carousel indicators.
+ * @return string the rendering result
+ */
+ public function renderIndicators()
+ {
+ $indicators = [];
+ for ($i = 0, $count = count($this->items); $i < $count; $i++) {
+ $options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i];
+ if ($i === 0) {
+ Html::addCssClass($options, 'active');
+ }
+ $indicators[] = Html::tag('li', '', $options);
+ }
- /**
- * Renders carousel indicators.
- * @return string the rendering result
- */
- public function renderIndicators()
- {
- $indicators = [];
- for ($i = 0, $count = count($this->items); $i < $count; $i++) {
- $options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i];
- if ($i === 0) {
- Html::addCssClass($options, 'active');
- }
- $indicators[] = Html::tag('li', '', $options);
- }
- return Html::tag('ol', implode("\n", $indicators), ['class' => 'carousel-indicators']);
- }
+ return Html::tag('ol', implode("\n", $indicators), ['class' => 'carousel-indicators']);
+ }
- /**
- * Renders carousel items as specified on [[items]].
- * @return string the rendering result
- */
- public function renderItems()
- {
- $items = [];
- for ($i = 0, $count = count($this->items); $i < $count; $i++) {
- $items[] = $this->renderItem($this->items[$i], $i);
- }
- return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']);
- }
+ /**
+ * Renders carousel items as specified on [[items]].
+ * @return string the rendering result
+ */
+ public function renderItems()
+ {
+ $items = [];
+ for ($i = 0, $count = count($this->items); $i < $count; $i++) {
+ $items[] = $this->renderItem($this->items[$i], $i);
+ }
- /**
- * Renders a single carousel item
- * @param string|array $item a single item from [[items]]
- * @param integer $index the item index as the first item should be set to `active`
- * @return string the rendering result
- * @throws InvalidConfigException if the item is invalid
- */
- public function renderItem($item, $index)
- {
- if (is_string($item)) {
- $content = $item;
- $caption = null;
- $options = [];
- } elseif (isset($item['content'])) {
- $content = $item['content'];
- $caption = ArrayHelper::getValue($item, 'caption');
- if ($caption !== null) {
- $caption = Html::tag('div', $caption, ['class' => 'carousel-caption']);
- }
- $options = ArrayHelper::getValue($item, 'options', []);
- } else {
- throw new InvalidConfigException('The "content" option is required.');
- }
+ return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']);
+ }
- Html::addCssClass($options, 'item');
- if ($index === 0) {
- Html::addCssClass($options, 'active');
- }
+ /**
+ * Renders a single carousel item
+ * @param string|array $item a single item from [[items]]
+ * @param integer $index the item index as the first item should be set to `active`
+ * @return string the rendering result
+ * @throws InvalidConfigException if the item is invalid
+ */
+ public function renderItem($item, $index)
+ {
+ if (is_string($item)) {
+ $content = $item;
+ $caption = null;
+ $options = [];
+ } elseif (isset($item['content'])) {
+ $content = $item['content'];
+ $caption = ArrayHelper::getValue($item, 'caption');
+ if ($caption !== null) {
+ $caption = Html::tag('div', $caption, ['class' => 'carousel-caption']);
+ }
+ $options = ArrayHelper::getValue($item, 'options', []);
+ } else {
+ throw new InvalidConfigException('The "content" option is required.');
+ }
- return Html::tag('div', $content . "\n" . $caption, $options);
- }
+ Html::addCssClass($options, 'item');
+ if ($index === 0) {
+ Html::addCssClass($options, 'active');
+ }
- /**
- * Renders previous and next control buttons.
- * @throws InvalidConfigException if [[controls]] is invalid.
- */
- public function renderControls()
- {
- if (isset($this->controls[0], $this->controls[1])) {
- return Html::a($this->controls[0], '#' . $this->options['id'], [
- 'class' => 'left carousel-control',
- 'data-slide' => 'prev',
- ]) . "\n"
- . Html::a($this->controls[1], '#' . $this->options['id'], [
- 'class' => 'right carousel-control',
- 'data-slide' => 'next',
- ]);
- } elseif ($this->controls === false) {
- return '';
- } else {
- throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.');
- }
- }
+ return Html::tag('div', $content . "\n" . $caption, $options);
+ }
+
+ /**
+ * Renders previous and next control buttons.
+ * @throws InvalidConfigException if [[controls]] is invalid.
+ */
+ public function renderControls()
+ {
+ if (isset($this->controls[0], $this->controls[1])) {
+ return Html::a($this->controls[0], '#' . $this->options['id'], [
+ 'class' => 'left carousel-control',
+ 'data-slide' => 'prev',
+ ]) . "\n"
+ . Html::a($this->controls[1], '#' . $this->options['id'], [
+ 'class' => 'right carousel-control',
+ 'data-slide' => 'next',
+ ]);
+ } elseif ($this->controls === false) {
+ return '';
+ } else {
+ throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.');
+ }
+ }
}
diff --git a/extensions/bootstrap/Collapse.php b/extensions/bootstrap/Collapse.php
index 2de06d97a3a..b8fb97c52cc 100644
--- a/extensions/bootstrap/Collapse.php
+++ b/extensions/bootstrap/Collapse.php
@@ -41,95 +41,94 @@
*/
class Collapse extends Widget
{
- /**
- * @var array list of groups in the collapse widget. Each array element represents a single
- * group with the following structure:
- *
- * ```php
- * // item key is the actual group header
- * 'Collapsible Group Item #1' => [
- * // required, the content (HTML) of the group
- * 'content' => 'Anim pariatur cliche...',
- * // optional the HTML attributes of the content group
- * 'contentOptions' => [],
- * // optional the HTML attributes of the group
- * 'options' => [],
- * ]
- * ```
- */
- public $items = [];
+ /**
+ * @var array list of groups in the collapse widget. Each array element represents a single
+ * group with the following structure:
+ *
+ * ```php
+ * // item key is the actual group header
+ * 'Collapsible Group Item #1' => [
+ * // required, the content (HTML) of the group
+ * 'content' => 'Anim pariatur cliche...',
+ * // optional the HTML attributes of the content group
+ * 'contentOptions' => [],
+ * // optional the HTML attributes of the group
+ * 'options' => [],
+ * ]
+ * ```
+ */
+ public $items = [];
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'panel-group');
+ }
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- Html::addCssClass($this->options, 'panel-group');
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderItems() . "\n";
+ echo Html::endTag('div') . "\n";
+ $this->registerPlugin('collapse');
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::beginTag('div', $this->options) . "\n";
- echo $this->renderItems() . "\n";
- echo Html::endTag('div') . "\n";
- $this->registerPlugin('collapse');
- }
+ /**
+ * Renders collapsible items as specified on [[items]].
+ * @return string the rendering result
+ */
+ public function renderItems()
+ {
+ $items = [];
+ $index = 0;
+ foreach ($this->items as $header => $item) {
+ $options = ArrayHelper::getValue($item, 'options', []);
+ Html::addCssClass($options, 'panel panel-default');
+ $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options);
+ }
- /**
- * Renders collapsible items as specified on [[items]].
- * @return string the rendering result
- */
- public function renderItems()
- {
- $items = [];
- $index = 0;
- foreach ($this->items as $header => $item) {
- $options = ArrayHelper::getValue($item, 'options', []);
- Html::addCssClass($options, 'panel panel-default');
- $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options);
- }
+ return implode("\n", $items);
+ }
- return implode("\n", $items);
- }
+ /**
+ * Renders a single collapsible item group
+ * @param string $header a label of the item group [[items]]
+ * @param array $item a single item from [[items]]
+ * @param integer $index the item index as each item group content must have an id
+ * @return string the rendering result
+ * @throws InvalidConfigException
+ */
+ public function renderItem($header, $item, $index)
+ {
+ if (isset($item['content'])) {
+ $id = $this->options['id'] . '-collapse' . $index;
+ $options = ArrayHelper::getValue($item, 'contentOptions', []);
+ $options['id'] = $id;
+ Html::addCssClass($options, 'panel-collapse collapse');
- /**
- * Renders a single collapsible item group
- * @param string $header a label of the item group [[items]]
- * @param array $item a single item from [[items]]
- * @param integer $index the item index as each item group content must have an id
- * @return string the rendering result
- * @throws InvalidConfigException
- */
- public function renderItem($header, $item, $index)
- {
- if (isset($item['content'])) {
- $id = $this->options['id'] . '-collapse' . $index;
- $options = ArrayHelper::getValue($item, 'contentOptions', []);
- $options['id'] = $id;
- Html::addCssClass($options, 'panel-collapse collapse');
+ $headerToggle = Html::a($header, '#' . $id, [
+ 'class' => 'collapse-toggle',
+ 'data-toggle' => 'collapse',
+ 'data-parent' => '#' . $this->options['id']
+ ]) . "\n";
- $headerToggle = Html::a($header, '#' . $id, [
- 'class' => 'collapse-toggle',
- 'data-toggle' => 'collapse',
- 'data-parent' => '#' . $this->options['id']
- ]) . "\n";
+ $header = Html::tag('h4', $headerToggle, ['class' => 'panel-title']);
- $header = Html::tag('h4', $headerToggle, ['class' => 'panel-title']);
+ $content = Html::tag('div', $item['content'], ['class' => 'panel-body']) . "\n";
+ } else {
+ throw new InvalidConfigException('The "content" option is required.');
+ }
+ $group = [];
- $content = Html::tag('div', $item['content'], ['class' => 'panel-body']) . "\n";
- } else {
- throw new InvalidConfigException('The "content" option is required.');
- }
- $group = [];
+ $group[] = Html::tag('div', $header, ['class' => 'panel-heading']);
+ $group[] = Html::tag('div', $content, $options);
- $group[] = Html::tag('div', $header, ['class' => 'panel-heading']);
- $group[] = Html::tag('div', $content, $options);
-
- return implode("\n", $group);
- }
+ return implode("\n", $group);
+ }
}
diff --git a/extensions/bootstrap/Dropdown.php b/extensions/bootstrap/Dropdown.php
index 85136b862cd..8109add6959 100644
--- a/extensions/bootstrap/Dropdown.php
+++ b/extensions/bootstrap/Dropdown.php
@@ -20,79 +20,79 @@
*/
class Dropdown extends Widget
{
- /**
- * @var array list of menu items in the dropdown. Each array element can be either an HTML string,
- * or an array representing a single menu with the following structure:
- *
- * - label: string, required, the label of the item link
- * - url: string, optional, the url of the item link. Defaults to "#".
- * - 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 ``.
- */
- public $items = [];
- /**
- * @var boolean whether the labels for header items should be HTML-encoded.
- */
- public $encodeLabels = true;
+ /**
+ * @var array list of menu items in the dropdown. Each array element can be either an HTML string,
+ * or an array representing a single menu with the following structure:
+ *
+ * - label: string, required, the label of the item link
+ * - url: string, optional, the url of the item link. Defaults to "#".
+ * - 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 ``.
+ */
+ public $items = [];
+ /**
+ * @var boolean whether the labels for header items should be HTML-encoded.
+ */
+ public $encodeLabels = true;
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- parent::init();
- Html::addCssClass($this->options, 'dropdown-menu');
- }
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'dropdown-menu');
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo $this->renderItems($this->items);
- $this->registerPlugin('dropdown');
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderItems($this->items);
+ $this->registerPlugin('dropdown');
+ }
- /**
- * Renders menu items.
- * @param array $items the menu items to be rendered
- * @return string the rendering result.
- * @throws InvalidConfigException if the label option is not specified in one of the items.
- */
- protected function renderItems($items)
- {
- $lines = [];
- foreach ($items as $i => $item) {
- if (isset($item['visible']) && !$item['visible']) {
- unset($items[$i]);
- continue;
- }
- if (is_string($item)) {
- $lines[] = $item;
- continue;
- }
- if (!isset($item['label'])) {
- throw new InvalidConfigException("The 'label' option is required.");
- }
- $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
- $options = ArrayHelper::getValue($item, 'options', []);
- $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);
- }
+ /**
+ * Renders menu items.
+ * @param array $items the menu items to be rendered
+ * @return string the rendering result.
+ * @throws InvalidConfigException if the label option is not specified in one of the items.
+ */
+ protected function renderItems($items)
+ {
+ $lines = [];
+ foreach ($items as $i => $item) {
+ if (isset($item['visible']) && !$item['visible']) {
+ unset($items[$i]);
+ continue;
+ }
+ if (is_string($item)) {
+ $lines[] = $item;
+ continue;
+ }
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
+ $options = ArrayHelper::getValue($item, 'options', []);
+ $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);
+ }
- return Html::tag('ul', implode("\n", $lines), $this->options);
- }
+ return Html::tag('ul', implode("\n", $lines), $this->options);
+ }
}
diff --git a/extensions/bootstrap/Modal.php b/extensions/bootstrap/Modal.php
index e498f492336..62352788bbf 100644
--- a/extensions/bootstrap/Modal.php
+++ b/extensions/bootstrap/Modal.php
@@ -35,201 +35,202 @@
*/
class Modal extends Widget
{
- const SIZE_LARGE = "modal-lg";
- const SIZE_SMALL = "modal-sm";
- const SIZE_DEFAULT = "";
-
- /**
- * @var string the header content in the modal window.
- */
- public $header;
- /**
- * @var string the footer content in the modal window.
- */
- public $footer;
- /**
- * @var string the modal size. Can be MODAL_LG or MODAL_SM, or empty for default.
- */
- public $size;
- /**
- * @var array the options for rendering the close button tag.
- * The close button is displayed in the header of the modal window. Clicking
- * on the button will hide the modal window. If this is null, no close button will be rendered.
- *
- * The following special options are supported:
- *
- * - tag: string, the tag name of the button. Defaults to 'button'.
- * - label: string, the label of the button. Defaults to '×'.
- *
- * The rest of the options will be rendered as the HTML attributes of the button tag.
- * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
- * for the supported HTML attributes.
- */
- public $closeButton = [];
- /**
- * @var array the options for rendering the toggle button tag.
- * The toggle button is used to toggle the visibility of the modal window.
- * If this property is null, no toggle button will be rendered.
- *
- * The following special options are supported:
- *
- * - tag: string, the tag name of the button. Defaults to 'button'.
- * - label: string, the label of the button. Defaults to 'Show'.
- *
- * The rest of the options will be rendered as the HTML attributes of the button tag.
- * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
- * for the supported HTML attributes.
- */
- public $toggleButton;
-
-
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
-
- $this->initOptions();
-
- echo $this->renderToggleButton() . "\n";
- echo Html::beginTag('div', $this->options) . "\n";
- echo Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n";
- echo Html::beginTag('div', ['class' => 'modal-content']) . "\n";
- echo $this->renderHeader() . "\n";
- echo $this->renderBodyBegin() . "\n";
- }
-
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo "\n" . $this->renderBodyEnd();
- echo "\n" . $this->renderFooter();
- echo "\n" . Html::endTag('div'); // modal-content
- echo "\n" . Html::endTag('div'); // modal-dialog
- echo "\n" . Html::endTag('div');
-
- $this->registerPlugin('modal');
- }
-
- /**
- * Renders the header HTML markup of the modal
- * @return string the rendering result
- */
- protected function renderHeader()
- {
- $button = $this->renderCloseButton();
- if ($button !== null) {
- $this->header = $button . "\n" . $this->header;
- }
- if ($this->header !== null) {
- return Html::tag('div', "\n" . $this->header . "\n", ['class' => 'modal-header']);
- } else {
- return null;
- }
- }
-
- /**
- * Renders the opening tag of the modal body.
- * @return string the rendering result
- */
- protected function renderBodyBegin()
- {
- return Html::beginTag('div', ['class' => 'modal-body']);
- }
-
- /**
- * Renders the closing tag of the modal body.
- * @return string the rendering result
- */
- protected function renderBodyEnd()
- {
- return Html::endTag('div');
- }
-
- /**
- * Renders the HTML markup for the footer of the modal
- * @return string the rendering result
- */
- protected function renderFooter()
- {
- if ($this->footer !== null) {
- return Html::tag('div', "\n" . $this->footer . "\n", ['class' => 'modal-footer']);
- } else {
- return null;
- }
- }
-
- /**
- * Renders the toggle button.
- * @return string the rendering result
- */
- protected function renderToggleButton()
- {
- if ($this->toggleButton !== null) {
- $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button');
- $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show');
- if ($tag === 'button' && !isset($this->toggleButton['type'])) {
- $this->toggleButton['type'] = 'button';
- }
- return Html::tag($tag, $label, $this->toggleButton);
- } else {
- return null;
- }
- }
-
- /**
- * Renders the close button.
- * @return string the rendering result
- */
- protected function renderCloseButton()
- {
- if ($this->closeButton !== null) {
- $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
- $label = ArrayHelper::remove($this->closeButton, 'label', '×');
- if ($tag === 'button' && !isset($this->closeButton['type'])) {
- $this->closeButton['type'] = 'button';
- }
- return Html::tag($tag, $label, $this->closeButton);
- } else {
- return null;
- }
- }
-
- /**
- * Initializes the widget options.
- * This method sets the default values for various options.
- */
- protected function initOptions()
- {
- $this->options = array_merge([
- 'class' => 'fade',
- 'role' => 'dialog',
- 'tabindex' => -1,
- ], $this->options);
- Html::addCssClass($this->options, 'modal');
-
- if ($this->clientOptions !== false) {
- $this->clientOptions = array_merge(['show' => false], $this->clientOptions);
- }
-
- if ($this->closeButton !== null) {
- $this->closeButton = array_merge([
- 'data-dismiss' => 'modal',
- 'aria-hidden' => 'true',
- 'class' => 'close',
- ], $this->closeButton);
- }
-
- if ($this->toggleButton !== null) {
- $this->toggleButton = array_merge([
- 'data-toggle' => 'modal',
- ], $this->toggleButton);
- if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) {
- $this->toggleButton['data-target'] = '#' . $this->options['id'];
- }
- }
- }
+ const SIZE_LARGE = "modal-lg";
+ const SIZE_SMALL = "modal-sm";
+ const SIZE_DEFAULT = "";
+
+ /**
+ * @var string the header content in the modal window.
+ */
+ public $header;
+ /**
+ * @var string the footer content in the modal window.
+ */
+ public $footer;
+ /**
+ * @var string the modal size. Can be MODAL_LG or MODAL_SM, or empty for default.
+ */
+ public $size;
+ /**
+ * @var array the options for rendering the close button tag.
+ * The close button is displayed in the header of the modal window. Clicking
+ * on the button will hide the modal window. If this is null, no close button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to '×'.
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
+ * for the supported HTML attributes.
+ */
+ public $closeButton = [];
+ /**
+ * @var array the options for rendering the toggle button tag.
+ * The toggle button is used to toggle the visibility of the modal window.
+ * If this property is null, no toggle button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to 'Show'.
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
+ * for the supported HTML attributes.
+ */
+ public $toggleButton;
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+
+ $this->initOptions();
+
+ echo $this->renderToggleButton() . "\n";
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n";
+ echo Html::beginTag('div', ['class' => 'modal-content']) . "\n";
+ echo $this->renderHeader() . "\n";
+ echo $this->renderBodyBegin() . "\n";
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo "\n" . $this->renderBodyEnd();
+ echo "\n" . $this->renderFooter();
+ echo "\n" . Html::endTag('div'); // modal-content
+ echo "\n" . Html::endTag('div'); // modal-dialog
+ echo "\n" . Html::endTag('div');
+
+ $this->registerPlugin('modal');
+ }
+
+ /**
+ * Renders the header HTML markup of the modal
+ * @return string the rendering result
+ */
+ protected function renderHeader()
+ {
+ $button = $this->renderCloseButton();
+ if ($button !== null) {
+ $this->header = $button . "\n" . $this->header;
+ }
+ if ($this->header !== null) {
+ return Html::tag('div', "\n" . $this->header . "\n", ['class' => 'modal-header']);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Renders the opening tag of the modal body.
+ * @return string the rendering result
+ */
+ protected function renderBodyBegin()
+ {
+ return Html::beginTag('div', ['class' => 'modal-body']);
+ }
+
+ /**
+ * Renders the closing tag of the modal body.
+ * @return string the rendering result
+ */
+ protected function renderBodyEnd()
+ {
+ return Html::endTag('div');
+ }
+
+ /**
+ * Renders the HTML markup for the footer of the modal
+ * @return string the rendering result
+ */
+ protected function renderFooter()
+ {
+ if ($this->footer !== null) {
+ return Html::tag('div', "\n" . $this->footer . "\n", ['class' => 'modal-footer']);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Renders the toggle button.
+ * @return string the rendering result
+ */
+ protected function renderToggleButton()
+ {
+ if ($this->toggleButton !== null) {
+ $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button');
+ $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show');
+ if ($tag === 'button' && !isset($this->toggleButton['type'])) {
+ $this->toggleButton['type'] = 'button';
+ }
+
+ return Html::tag($tag, $label, $this->toggleButton);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Renders the close button.
+ * @return string the rendering result
+ */
+ protected function renderCloseButton()
+ {
+ if ($this->closeButton !== null) {
+ $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
+ $label = ArrayHelper::remove($this->closeButton, 'label', '×');
+ if ($tag === 'button' && !isset($this->closeButton['type'])) {
+ $this->closeButton['type'] = 'button';
+ }
+
+ return Html::tag($tag, $label, $this->closeButton);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions()
+ {
+ $this->options = array_merge([
+ 'class' => 'fade',
+ 'role' => 'dialog',
+ 'tabindex' => -1,
+ ], $this->options);
+ Html::addCssClass($this->options, 'modal');
+
+ if ($this->clientOptions !== false) {
+ $this->clientOptions = array_merge(['show' => false], $this->clientOptions);
+ }
+
+ if ($this->closeButton !== null) {
+ $this->closeButton = array_merge([
+ 'data-dismiss' => 'modal',
+ 'aria-hidden' => 'true',
+ 'class' => 'close',
+ ], $this->closeButton);
+ }
+
+ if ($this->toggleButton !== null) {
+ $this->toggleButton = array_merge([
+ 'data-toggle' => 'modal',
+ ], $this->toggleButton);
+ if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) {
+ $this->toggleButton['data-target'] = '#' . $this->options['id'];
+ }
+ }
+ }
}
diff --git a/extensions/bootstrap/Nav.php b/extensions/bootstrap/Nav.php
index 9d4afc03bd4..191578cd2a9 100644
--- a/extensions/bootstrap/Nav.php
+++ b/extensions/bootstrap/Nav.php
@@ -48,168 +48,169 @@
*/
class Nav extends Widget
{
- /**
- * @var array list of items in the nav widget. Each array element represents a single
- * menu item which can be either a string or an array with the following structure:
- *
- * - label: string, required, the nav item label.
- * - url: optional, the item's URL. Defaults to "#".
- * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
- * - linkOptions: array, optional, the HTML attributes of the item's link.
- * - options: array, optional, the HTML attributes of the item container (LI).
- * - active: boolean, optional, whether the item should be on active state or not.
- * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget,
- * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus.
- *
- * If a menu item is a string, it will be rendered directly without HTML encoding.
- */
- public $items = [];
- /**
- * @var boolean whether the nav items labels should be HTML-encoded.
- */
- public $encodeLabels = true;
- /**
- * @var boolean whether to automatically activate items according to whether their route setting
- * matches the currently requested route.
- * @see isItemActive
- */
- public $activateItems = true;
- /**
- * @var string the route used to determine if a menu item is active or not.
- * If not set, it will use the route of the current request.
- * @see params
- * @see isItemActive
- */
- public $route;
- /**
- * @var array the parameters used to determine if a menu item is active or not.
- * If not set, it will use `$_GET`.
- * @see route
- * @see isItemActive
- */
- public $params;
-
-
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- if ($this->route === null && Yii::$app->controller !== null) {
- $this->route = Yii::$app->controller->getRoute();
- }
- if ($this->params === null) {
- $this->params = Yii::$app->request->getQueryParams();
- }
- Html::addCssClass($this->options, 'nav');
- }
-
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo $this->renderItems();
- BootstrapAsset::register($this->getView());
- }
-
- /**
- * Renders widget items.
- */
- public function renderItems()
- {
- $items = [];
- foreach ($this->items as $i => $item) {
- if (isset($item['visible']) && !$item['visible']) {
- unset($items[$i]);
- continue;
- }
- $items[] = $this->renderItem($item);
- }
-
- return Html::tag('ul', implode("\n", $items), $this->options);
- }
-
- /**
- * Renders a widget's item.
- * @param string|array $item the item to render.
- * @return string the rendering result.
- * @throws InvalidConfigException
- */
- public function renderItem($item)
- {
- if (is_string($item)) {
- return $item;
- }
- if (!isset($item['label'])) {
- throw new InvalidConfigException("The 'label' option is required.");
- }
- $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
- $options = ArrayHelper::getValue($item, 'options', []);
- $items = ArrayHelper::getValue($item, 'items');
- $url = ArrayHelper::getValue($item, 'url', '#');
- $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
-
- if (isset($item['active'])) {
- $active = ArrayHelper::remove($item, 'active', false);
- } else {
- $active = $this->isItemActive($item);
- }
-
- if ($active) {
- Html::addCssClass($options, 'active');
- }
-
- if ($items !== null) {
- $linkOptions['data-toggle'] = 'dropdown';
- Html::addCssClass($options, 'dropdown');
- Html::addCssClass($linkOptions, 'dropdown-toggle');
- $label .= ' ' . Html::tag('b', '', ['class' => 'caret']);
- if (is_array($items)) {
- $items = Dropdown::widget([
- 'items' => $items,
- 'encodeLabels' => $this->encodeLabels,
- 'clientOptions' => false,
- 'view' => $this->getView(),
- ]);
- }
- }
-
- return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
- }
-
-
- /**
- * Checks whether a menu item is active.
- * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
- * When the `url` option of a menu item is specified in terms of an array, its first element is treated
- * as the route for the item and the rest of the elements are the associated parameters.
- * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
- * be considered active.
- * @param array $item the menu item to be checked
- * @return boolean whether the menu item is active
- */
- protected function isItemActive($item)
- {
- if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
- $route = $item['url'][0];
- if ($route[0] !== '/' && Yii::$app->controller) {
- $route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
- }
- if (ltrim($route, '/') !== $this->route) {
- return false;
- }
- unset($item['url']['#']);
- if (count($item['url']) > 1) {
- foreach (array_splice($item['url'], 1) as $name => $value) {
- if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) {
- return false;
- }
- }
- }
- return true;
- }
- return false;
- }
+ /**
+ * @var array list of items in the nav widget. Each array element represents a single
+ * menu item which can be either a string or an array with the following structure:
+ *
+ * - label: string, required, the nav item label.
+ * - url: optional, the item's URL. Defaults to "#".
+ * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
+ * - linkOptions: array, optional, the HTML attributes of the item's link.
+ * - options: array, optional, the HTML attributes of the item container (LI).
+ * - active: boolean, optional, whether the item should be on active state or not.
+ * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget,
+ * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus.
+ *
+ * If a menu item is a string, it will be rendered directly without HTML encoding.
+ */
+ public $items = [];
+ /**
+ * @var boolean whether the nav items labels should be HTML-encoded.
+ */
+ public $encodeLabels = true;
+ /**
+ * @var boolean whether to automatically activate items according to whether their route setting
+ * matches the currently requested route.
+ * @see isItemActive
+ */
+ public $activateItems = true;
+ /**
+ * @var string the route used to determine if a menu item is active or not.
+ * If not set, it will use the route of the current request.
+ * @see params
+ * @see isItemActive
+ */
+ public $route;
+ /**
+ * @var array the parameters used to determine if a menu item is active or not.
+ * If not set, it will use `$_GET`.
+ * @see route
+ * @see isItemActive
+ */
+ public $params;
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->route === null && Yii::$app->controller !== null) {
+ $this->route = Yii::$app->controller->getRoute();
+ }
+ if ($this->params === null) {
+ $this->params = Yii::$app->request->getQueryParams();
+ }
+ Html::addCssClass($this->options, 'nav');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderItems();
+ BootstrapAsset::register($this->getView());
+ }
+
+ /**
+ * Renders widget items.
+ */
+ public function renderItems()
+ {
+ $items = [];
+ foreach ($this->items as $i => $item) {
+ if (isset($item['visible']) && !$item['visible']) {
+ unset($items[$i]);
+ continue;
+ }
+ $items[] = $this->renderItem($item);
+ }
+
+ return Html::tag('ul', implode("\n", $items), $this->options);
+ }
+
+ /**
+ * Renders a widget's item.
+ * @param string|array $item the item to render.
+ * @return string the rendering result.
+ * @throws InvalidConfigException
+ */
+ public function renderItem($item)
+ {
+ if (is_string($item)) {
+ return $item;
+ }
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
+ $options = ArrayHelper::getValue($item, 'options', []);
+ $items = ArrayHelper::getValue($item, 'items');
+ $url = ArrayHelper::getValue($item, 'url', '#');
+ $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
+
+ if (isset($item['active'])) {
+ $active = ArrayHelper::remove($item, 'active', false);
+ } else {
+ $active = $this->isItemActive($item);
+ }
+
+ if ($active) {
+ Html::addCssClass($options, 'active');
+ }
+
+ if ($items !== null) {
+ $linkOptions['data-toggle'] = 'dropdown';
+ Html::addCssClass($options, 'dropdown');
+ Html::addCssClass($linkOptions, 'dropdown-toggle');
+ $label .= ' ' . Html::tag('b', '', ['class' => 'caret']);
+ if (is_array($items)) {
+ $items = Dropdown::widget([
+ 'items' => $items,
+ 'encodeLabels' => $this->encodeLabels,
+ 'clientOptions' => false,
+ 'view' => $this->getView(),
+ ]);
+ }
+ }
+
+ return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
+ }
+
+
+ /**
+ * Checks whether a menu item is active.
+ * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
+ * When the `url` option of a menu item is specified in terms of an array, its first element is treated
+ * as the route for the item and the rest of the elements are the associated parameters.
+ * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
+ * be considered active.
+ * @param array $item the menu item to be checked
+ * @return boolean whether the menu item is active
+ */
+ protected function isItemActive($item)
+ {
+ if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
+ $route = $item['url'][0];
+ if ($route[0] !== '/' && Yii::$app->controller) {
+ $route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
+ }
+ if (ltrim($route, '/') !== $this->route) {
+ return false;
+ }
+ unset($item['url']['#']);
+ if (count($item['url']) > 1) {
+ foreach (array_splice($item['url'], 1) as $name => $value) {
+ if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/extensions/bootstrap/NavBar.php b/extensions/bootstrap/NavBar.php
index d1396a21e3c..ab681d189f0 100644
--- a/extensions/bootstrap/NavBar.php
+++ b/extensions/bootstrap/NavBar.php
@@ -39,120 +39,121 @@
*/
class NavBar extends Widget
{
- /**
- * @var array the HTML attributes for the widget container tag. The following special options are recognized:
- *
- * - tag: string, defaults to "nav", the name of the container tag.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array the HTML attributes for the container tag. The following special options are recognized:
- *
- * - tag: string, defaults to "div", the name of the container tag.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $containerOptions = [];
- /**
- * @var string the text of the brand. Note that this is not HTML-encoded.
- * @see http://getbootstrap.com/components/#navbar
- */
- public $brandLabel;
- /**
- * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Url::to()]]
- * and will be used for the "href" attribute of the brand link. If not set, [[\yii\web\Application::homeUrl]] will be used.
- */
- public $brandUrl;
- /**
- * @var array the HTML attributes of the brand link.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $brandOptions = [];
- /**
- * @var string text to show for screen readers for the button to toggle the navbar.
- */
- public $screenReaderToggleText = 'Toggle navigation';
- /**
- * @var boolean whether the navbar content should be included in an inner div container which by default
- * adds left and right padding. Set this to false for a 100% width navbar.
- */
- public $renderInnerContainer = true;
- /**
- * @var array the HTML attributes of the inner container.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $innerContainerOptions = [];
+ /**
+ * @var array the HTML attributes for the widget container tag. The following special options are recognized:
+ *
+ * - tag: string, defaults to "nav", the name of the container tag.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array the HTML attributes for the container tag. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div", the name of the container tag.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $containerOptions = [];
+ /**
+ * @var string the text of the brand. Note that this is not HTML-encoded.
+ * @see http://getbootstrap.com/components/#navbar
+ */
+ public $brandLabel;
+ /**
+ * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Url::to()]]
+ * and will be used for the "href" attribute of the brand link. If not set, [[\yii\web\Application::homeUrl]] will be used.
+ */
+ public $brandUrl;
+ /**
+ * @var array the HTML attributes of the brand link.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $brandOptions = [];
+ /**
+ * @var string text to show for screen readers for the button to toggle the navbar.
+ */
+ public $screenReaderToggleText = 'Toggle navigation';
+ /**
+ * @var boolean whether the navbar content should be included in an inner div container which by default
+ * adds left and right padding. Set this to false for a 100% width navbar.
+ */
+ public $renderInnerContainer = true;
+ /**
+ * @var array the HTML attributes of the inner container.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $innerContainerOptions = [];
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- $this->clientOptions = false;
- Html::addCssClass($this->options, 'navbar');
- if ($this->options['class'] === 'navbar') {
- Html::addCssClass($this->options, 'navbar-default');
- }
- Html::addCssClass($this->brandOptions, 'navbar-brand');
- if (empty($this->options['role'])) {
- $this->options['role'] = 'navigation';
- }
- $options = $this->options;
- $tag = ArrayHelper::remove($options, 'tag', 'nav');
- echo Html::beginTag($tag, $options);
- if ($this->renderInnerContainer) {
- if (!isset($this->innerContainerOptions['class'])) {
- Html::addCssClass($this->innerContainerOptions, 'container');
- }
- echo Html::beginTag('div', $this->innerContainerOptions);
- }
- echo Html::beginTag('div', ['class' => 'navbar-header']);
- if (!isset($this->containerOptions['id'])) {
- $this->containerOptions['id'] = "{$this->options['id']}-collapse";
- }
- echo $this->renderToggleButton();
- if ($this->brandLabel !== null) {
- Html::addCssClass($this->brandOptions, 'navbar-brand');
- echo Html::a($this->brandLabel, $this->brandUrl === null ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions);
- }
- echo Html::endTag('div');
- Html::addCssClass($this->containerOptions, 'collapse');
- Html::addCssClass($this->containerOptions, 'navbar-collapse');
- $options = $this->containerOptions;
- $tag = ArrayHelper::remove($options, 'tag', 'div');
- echo Html::beginTag($tag, $options);
- }
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->clientOptions = false;
+ Html::addCssClass($this->options, 'navbar');
+ if ($this->options['class'] === 'navbar') {
+ Html::addCssClass($this->options, 'navbar-default');
+ }
+ Html::addCssClass($this->brandOptions, 'navbar-brand');
+ if (empty($this->options['role'])) {
+ $this->options['role'] = 'navigation';
+ }
+ $options = $this->options;
+ $tag = ArrayHelper::remove($options, 'tag', 'nav');
+ echo Html::beginTag($tag, $options);
+ if ($this->renderInnerContainer) {
+ if (!isset($this->innerContainerOptions['class'])) {
+ Html::addCssClass($this->innerContainerOptions, 'container');
+ }
+ echo Html::beginTag('div', $this->innerContainerOptions);
+ }
+ echo Html::beginTag('div', ['class' => 'navbar-header']);
+ if (!isset($this->containerOptions['id'])) {
+ $this->containerOptions['id'] = "{$this->options['id']}-collapse";
+ }
+ echo $this->renderToggleButton();
+ if ($this->brandLabel !== null) {
+ Html::addCssClass($this->brandOptions, 'navbar-brand');
+ echo Html::a($this->brandLabel, $this->brandUrl === null ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions);
+ }
+ echo Html::endTag('div');
+ Html::addCssClass($this->containerOptions, 'collapse');
+ Html::addCssClass($this->containerOptions, 'navbar-collapse');
+ $options = $this->containerOptions;
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ echo Html::beginTag($tag, $options);
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- $tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div');
- echo Html::endTag($tag);
- if ($this->renderInnerContainer) {
- echo Html::endTag('div');
- }
- $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
- echo Html::endTag($tag, $this->options);
- BootstrapPluginAsset::register($this->getView());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ $tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div');
+ echo Html::endTag($tag);
+ if ($this->renderInnerContainer) {
+ echo Html::endTag('div');
+ }
+ $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
+ echo Html::endTag($tag, $this->options);
+ BootstrapPluginAsset::register($this->getView());
+ }
- /**
- * Renders collapsible toggle button.
- * @return string the rendering toggle button.
- */
- protected function renderToggleButton()
- {
- $bar = Html::tag('span', '', ['class' => 'icon-bar']);
- $screenReader = "{$this->screenReaderToggleText}";
- return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [
- 'class' => 'navbar-toggle',
- 'data-toggle' => 'collapse',
- 'data-target' => "#{$this->containerOptions['id']}",
- ]);
- }
+ /**
+ * Renders collapsible toggle button.
+ * @return string the rendering toggle button.
+ */
+ protected function renderToggleButton()
+ {
+ $bar = Html::tag('span', '', ['class' => 'icon-bar']);
+ $screenReader = "{$this->screenReaderToggleText}";
+
+ return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [
+ 'class' => 'navbar-toggle',
+ 'data-toggle' => 'collapse',
+ 'data-target' => "#{$this->containerOptions['id']}",
+ ]);
+ }
}
diff --git a/extensions/bootstrap/Progress.php b/extensions/bootstrap/Progress.php
index 33b7bb160b0..ec0827aa561 100644
--- a/extensions/bootstrap/Progress.php
+++ b/extensions/bootstrap/Progress.php
@@ -59,105 +59,106 @@
*/
class Progress extends Widget
{
- /**
- * @var string the button label.
- */
- public $label;
- /**
- * @var integer the amount of progress as a percentage.
- */
- public $percent = 0;
- /**
- * @var array the HTML attributes of the bar.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $barOptions = [];
- /**
- * @var array a set of bars that are stacked together to form a single progress bar.
- * Each bar is an array of the following structure:
- *
- * ```php
- * [
- * // required, the amount of progress as a percentage.
- * 'percent' => 30,
- * // optional, the label to be displayed on the bar
- * 'label' => '30%',
- * // optional, array, additional HTML attributes for the bar tag
- * 'options' => [],
- * ]
- * ```
- */
- public $bars;
+ /**
+ * @var string the button label.
+ */
+ public $label;
+ /**
+ * @var integer the amount of progress as a percentage.
+ */
+ public $percent = 0;
+ /**
+ * @var array the HTML attributes of the bar.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $barOptions = [];
+ /**
+ * @var array a set of bars that are stacked together to form a single progress bar.
+ * Each bar is an array of the following structure:
+ *
+ * ```php
+ * [
+ * // required, the amount of progress as a percentage.
+ * 'percent' => 30,
+ * // optional, the label to be displayed on the bar
+ * 'label' => '30%',
+ * // optional, array, additional HTML attributes for the bar tag
+ * 'options' => [],
+ * ]
+ * ```
+ */
+ public $bars;
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'progress');
+ }
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- parent::init();
- Html::addCssClass($this->options, 'progress');
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderProgress() . "\n";
+ echo Html::endTag('div') . "\n";
+ BootstrapAsset::register($this->getView());
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::beginTag('div', $this->options) . "\n";
- echo $this->renderProgress() . "\n";
- echo Html::endTag('div') . "\n";
- BootstrapAsset::register($this->getView());
- }
+ /**
+ * Renders the progress.
+ * @return string the rendering result.
+ * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
+ */
+ protected function renderProgress()
+ {
+ if (empty($this->bars)) {
+ return $this->renderBar($this->percent, $this->label, $this->barOptions);
+ }
+ $bars = [];
+ foreach ($this->bars as $bar) {
+ $label = ArrayHelper::getValue($bar, 'label', '');
+ if (!isset($bar['percent'])) {
+ throw new InvalidConfigException("The 'percent' option is required.");
+ }
+ $options = ArrayHelper::getValue($bar, 'options', []);
+ $bars[] = $this->renderBar($bar['percent'], $label, $options);
+ }
- /**
- * Renders the progress.
- * @return string the rendering result.
- * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
- */
- protected function renderProgress()
- {
- if (empty($this->bars)) {
- return $this->renderBar($this->percent, $this->label, $this->barOptions);
- }
- $bars = [];
- foreach ($this->bars as $bar) {
- $label = ArrayHelper::getValue($bar, 'label', '');
- if (!isset($bar['percent'])) {
- throw new InvalidConfigException("The 'percent' option is required.");
- }
- $options = ArrayHelper::getValue($bar, 'options', []);
- $bars[] = $this->renderBar($bar['percent'], $label, $options);
- }
- return implode("\n", $bars);
- }
+ return implode("\n", $bars);
+ }
- /**
- * Generates a bar
- * @param integer $percent the percentage of the bar
- * @param string $label, optional, the label to display at the bar
- * @param array $options the HTML attributes of the bar
- * @return string the rendering result.
- */
- protected function renderBar($percent, $label = '', $options = [])
- {
- $defaultOptions = [
- 'role' => 'progressbar',
- 'aria-valuenow' => $percent,
- 'aria-valuemin' => 0,
- 'aria-valuemax' => 100,
- 'style' => "width:{$percent}%",
- ];
- $options = array_merge($defaultOptions, $options);
- Html::addCssClass($options, 'progress-bar');
+ /**
+ * Generates a bar
+ * @param integer $percent the percentage of the bar
+ * @param string $label, optional, the label to display at the bar
+ * @param array $options the HTML attributes of the bar
+ * @return string the rendering result.
+ */
+ protected function renderBar($percent, $label = '', $options = [])
+ {
+ $defaultOptions = [
+ 'role' => 'progressbar',
+ 'aria-valuenow' => $percent,
+ 'aria-valuemin' => 0,
+ 'aria-valuemax' => 100,
+ 'style' => "width:{$percent}%",
+ ];
+ $options = array_merge($defaultOptions, $options);
+ Html::addCssClass($options, 'progress-bar');
- $out = Html::beginTag('div', $options);
- $out .= $label;
- $out .= Html::tag('span', \Yii::t('yii', '{percent}% Complete', ['percent' => $percent]), [
- 'class' => 'sr-only'
- ]);
- $out .= Html::endTag('div');
- return $out;
- }
+ $out = Html::beginTag('div', $options);
+ $out .= $label;
+ $out .= Html::tag('span', \Yii::t('yii', '{percent}% Complete', ['percent' => $percent]), [
+ 'class' => 'sr-only'
+ ]);
+ $out .= Html::endTag('div');
+
+ return $out;
+ }
}
diff --git a/extensions/bootstrap/Tabs.php b/extensions/bootstrap/Tabs.php
index 6c7fd2108ef..7c7274a22fc 100644
--- a/extensions/bootstrap/Tabs.php
+++ b/extensions/bootstrap/Tabs.php
@@ -53,179 +53,180 @@
*/
class Tabs extends Widget
{
- /**
- * @var array list of tabs in the tabs widget. Each array element represents a single
- * tab with the following structure:
- *
- * - label: string, required, the tab header label.
- * - headerOptions: array, optional, the HTML attributes of the tab header.
- * - linkOptions: array, optional, the HTML attributes of the tab header link tags.
- * - content: array, required if `items` is not set. The content (HTML) of the tab pane.
- * - options: array, optional, the HTML attributes of the tab pane container.
- * - active: boolean, optional, whether the item tab header and pane should be visible or not.
- * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items
- * configuration array. Each item can hold two extra keys, besides the above ones:
- * * active: boolean, optional, whether the item tab header and pane should be visible or not.
- * * content: string, required if `items` is not set. The content (HTML) of the tab pane.
- * * contentOptions: optional, array, the HTML attributes of the tab content container.
- */
- public $items = [];
- /**
- * @var array list of HTML attributes for the item container tags. This will be overwritten
- * by the "options" set in individual [[items]]. The following special options are recognized:
- *
- * - tag: string, defaults to "div", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $itemOptions = [];
- /**
- * @var array list of HTML attributes for the header container tags. This will be overwritten
- * by the "headerOptions" set in individual [[items]].
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $headerOptions = [];
- /**
- * @var array list of HTML attributes for the tab header link tags. This will be overwritten
- * by the "linkOptions" set in individual [[items]].
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $linkOptions = [];
- /**
- * @var boolean whether the labels for header items should be HTML-encoded.
- */
- public $encodeLabels = true;
- /**
- * @var string specifies the Bootstrap tab styling.
- */
- public $navType = 'nav-tabs';
-
-
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- Html::addCssClass($this->options, 'nav ' . $this->navType);
- }
-
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo $this->renderItems();
- $this->registerPlugin('tab');
- }
-
- /**
- * Renders tab items as specified on [[items]].
- * @return string the rendering result.
- * @throws InvalidConfigException.
- */
- protected function renderItems()
- {
- $headers = [];
- $panes = [];
-
- if (!$this->hasActiveTab() && !empty($this->items)) {
- $this->items[0]['active'] = true;
- }
-
- foreach ($this->items as $n => $item) {
- if (!isset($item['label'])) {
- throw new InvalidConfigException("The 'label' option is required.");
- }
- $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
- $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
- $linkOptions = array_merge($this->linkOptions, ArrayHelper::getValue($item, 'linkOptions', []));
-
- if (isset($item['items'])) {
- $label .= ' ';
- Html::addCssClass($headerOptions, 'dropdown');
-
- if ($this->renderDropdown($item['items'], $panes)) {
- Html::addCssClass($headerOptions, 'active');
- }
-
- Html::addCssClass($linkOptions, 'dropdown-toggle');
- $linkOptions['data-toggle'] = 'dropdown';
- $header = Html::a($label, "#", $linkOptions) . "\n"
- . Dropdown::widget(['items' => $item['items'], 'clientOptions' => false, 'view' => $this->getView()]);
- } elseif (isset($item['content'])) {
- $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
- $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n);
-
- Html::addCssClass($options, 'tab-pane');
- if (ArrayHelper::remove($item, 'active')) {
- Html::addCssClass($options, 'active');
- Html::addCssClass($headerOptions, 'active');
- }
- $linkOptions['data-toggle'] = 'tab';
- $header = Html::a($label, '#' . $options['id'], $linkOptions);
- $panes[] = Html::tag('div', $item['content'], $options);
- } else {
- throw new InvalidConfigException("Either the 'content' or 'items' option must be set.");
- }
-
- $headers[] = Html::tag('li', $header, $headerOptions);
- }
-
- return Html::tag('ul', implode("\n", $headers), $this->options) . "\n"
- . Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']);
- }
-
- /**
- * @return boolean if there's active tab defined
- */
- protected function hasActiveTab()
- {
- foreach ($this->items as $item) {
- if (isset($item['active']) && $item['active']===true) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also
- * configure `panes` accordingly.
- * @param array $items the dropdown items configuration.
- * @param array $panes the panes reference array.
- * @return boolean whether any of the dropdown items is `active` or not.
- * @throws InvalidConfigException
- */
- protected function renderDropdown(&$items, &$panes)
- {
- $itemActive = false;
-
- foreach ($items as $n => &$item) {
- if (is_string($item)) {
- continue;
- }
- if (!isset($item['content'])) {
- throw new InvalidConfigException("The 'content' option is required.");
- }
-
- $content = ArrayHelper::remove($item, 'content');
- $options = ArrayHelper::remove($item, 'contentOptions', []);
- Html::addCssClass($options, 'tab-pane');
- if (ArrayHelper::remove($item, 'active')) {
- Html::addCssClass($options, 'active');
- Html::addCssClass($item['options'], 'active');
- $itemActive = true;
- }
-
- $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n);
- $item['url'] = '#' . $options['id'];
- $item['linkOptions']['data-toggle'] = 'tab';
-
- $panes[] = Html::tag('div', $content, $options);
-
- unset($item);
- }
- return $itemActive;
- }
+ /**
+ * @var array list of tabs in the tabs widget. Each array element represents a single
+ * tab with the following structure:
+ *
+ * - label: string, required, the tab header label.
+ * - headerOptions: array, optional, the HTML attributes of the tab header.
+ * - linkOptions: array, optional, the HTML attributes of the tab header link tags.
+ * - content: array, required if `items` is not set. The content (HTML) of the tab pane.
+ * - options: array, optional, the HTML attributes of the tab pane container.
+ * - active: boolean, optional, whether the item tab header and pane should be visible or not.
+ * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items
+ * configuration array. Each item can hold two extra keys, besides the above ones:
+ * * active: boolean, optional, whether the item tab header and pane should be visible or not.
+ * * content: string, required if `items` is not set. The content (HTML) of the tab pane.
+ * * contentOptions: optional, array, the HTML attributes of the tab content container.
+ */
+ public $items = [];
+ /**
+ * @var array list of HTML attributes for the item container tags. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div", the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $itemOptions = [];
+ /**
+ * @var array list of HTML attributes for the header container tags. This will be overwritten
+ * by the "headerOptions" set in individual [[items]].
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $headerOptions = [];
+ /**
+ * @var array list of HTML attributes for the tab header link tags. This will be overwritten
+ * by the "linkOptions" set in individual [[items]].
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $linkOptions = [];
+ /**
+ * @var boolean whether the labels for header items should be HTML-encoded.
+ */
+ public $encodeLabels = true;
+ /**
+ * @var string specifies the Bootstrap tab styling.
+ */
+ public $navType = 'nav-tabs';
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'nav ' . $this->navType);
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderItems();
+ $this->registerPlugin('tab');
+ }
+
+ /**
+ * Renders tab items as specified on [[items]].
+ * @return string the rendering result.
+ * @throws InvalidConfigException.
+ */
+ protected function renderItems()
+ {
+ $headers = [];
+ $panes = [];
+
+ if (!$this->hasActiveTab() && !empty($this->items)) {
+ $this->items[0]['active'] = true;
+ }
+
+ foreach ($this->items as $n => $item) {
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
+ $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
+ $linkOptions = array_merge($this->linkOptions, ArrayHelper::getValue($item, 'linkOptions', []));
+
+ if (isset($item['items'])) {
+ $label .= ' ';
+ Html::addCssClass($headerOptions, 'dropdown');
+
+ if ($this->renderDropdown($item['items'], $panes)) {
+ Html::addCssClass($headerOptions, 'active');
+ }
+
+ Html::addCssClass($linkOptions, 'dropdown-toggle');
+ $linkOptions['data-toggle'] = 'dropdown';
+ $header = Html::a($label, "#", $linkOptions) . "\n"
+ . Dropdown::widget(['items' => $item['items'], 'clientOptions' => false, 'view' => $this->getView()]);
+ } elseif (isset($item['content'])) {
+ $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
+ $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n);
+
+ Html::addCssClass($options, 'tab-pane');
+ if (ArrayHelper::remove($item, 'active')) {
+ Html::addCssClass($options, 'active');
+ Html::addCssClass($headerOptions, 'active');
+ }
+ $linkOptions['data-toggle'] = 'tab';
+ $header = Html::a($label, '#' . $options['id'], $linkOptions);
+ $panes[] = Html::tag('div', $item['content'], $options);
+ } else {
+ throw new InvalidConfigException("Either the 'content' or 'items' option must be set.");
+ }
+
+ $headers[] = Html::tag('li', $header, $headerOptions);
+ }
+
+ return Html::tag('ul', implode("\n", $headers), $this->options) . "\n"
+ . Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']);
+ }
+
+ /**
+ * @return boolean if there's active tab defined
+ */
+ protected function hasActiveTab()
+ {
+ foreach ($this->items as $item) {
+ if (isset($item['active']) && $item['active']===true) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also
+ * configure `panes` accordingly.
+ * @param array $items the dropdown items configuration.
+ * @param array $panes the panes reference array.
+ * @return boolean whether any of the dropdown items is `active` or not.
+ * @throws InvalidConfigException
+ */
+ protected function renderDropdown(&$items, &$panes)
+ {
+ $itemActive = false;
+
+ foreach ($items as $n => &$item) {
+ if (is_string($item)) {
+ continue;
+ }
+ if (!isset($item['content'])) {
+ throw new InvalidConfigException("The 'content' option is required.");
+ }
+
+ $content = ArrayHelper::remove($item, 'content');
+ $options = ArrayHelper::remove($item, 'contentOptions', []);
+ Html::addCssClass($options, 'tab-pane');
+ if (ArrayHelper::remove($item, 'active')) {
+ Html::addCssClass($options, 'active');
+ Html::addCssClass($item['options'], 'active');
+ $itemActive = true;
+ }
+
+ $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n);
+ $item['url'] = '#' . $options['id'];
+ $item['linkOptions']['data-toggle'] = 'tab';
+
+ $panes[] = Html::tag('div', $content, $options);
+
+ unset($item);
+ }
+
+ return $itemActive;
+ }
}
diff --git a/extensions/bootstrap/Widget.php b/extensions/bootstrap/Widget.php
index e135737e8a0..2984e7a9a22 100644
--- a/extensions/bootstrap/Widget.php
+++ b/extensions/bootstrap/Widget.php
@@ -19,64 +19,63 @@
*/
class Widget extends \yii\base\Widget
{
- /**
- * @var array the HTML attributes for the widget container tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array the options for the underlying Bootstrap JS plugin.
- * Please refer to the corresponding Bootstrap plugin Web page for possible options.
- * For example, [this page](http://getbootstrap.com/javascript/#modals) shows
- * how to use the "Modal" plugin and the supported options (e.g. "remote").
- */
- public $clientOptions = [];
- /**
- * @var array the event handlers for the underlying Bootstrap JS plugin.
- * Please refer to the corresponding Bootstrap plugin Web page for possible events.
- * For example, [this page](http://getbootstrap.com/javascript/#modals) shows
- * how to use the "Modal" plugin and the supported events (e.g. "shown").
- */
- public $clientEvents = [];
+ /**
+ * @var array the HTML attributes for the widget container tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array the options for the underlying Bootstrap JS plugin.
+ * Please refer to the corresponding Bootstrap plugin Web page for possible options.
+ * For example, [this page](http://getbootstrap.com/javascript/#modals) shows
+ * how to use the "Modal" plugin and the supported options (e.g. "remote").
+ */
+ public $clientOptions = [];
+ /**
+ * @var array the event handlers for the underlying Bootstrap JS plugin.
+ * Please refer to the corresponding Bootstrap plugin Web page for possible events.
+ * For example, [this page](http://getbootstrap.com/javascript/#modals) shows
+ * how to use the "Modal" plugin and the supported events (e.g. "shown").
+ */
+ public $clientEvents = [];
+ /**
+ * Initializes the widget.
+ * This method will register the bootstrap asset bundle. If you override this method,
+ * make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->getId();
+ }
+ }
- /**
- * Initializes the widget.
- * This method will register the bootstrap asset bundle. If you override this method,
- * make sure you call the parent implementation first.
- */
- public function init()
- {
- parent::init();
- if (!isset($this->options['id'])) {
- $this->options['id'] = $this->getId();
- }
- }
+ /**
+ * Registers a specific Bootstrap plugin and the related events
+ * @param string $name the name of the Bootstrap plugin
+ */
+ protected function registerPlugin($name)
+ {
+ $view = $this->getView();
- /**
- * Registers a specific Bootstrap plugin and the related events
- * @param string $name the name of the Bootstrap plugin
- */
- protected function registerPlugin($name)
- {
- $view = $this->getView();
+ BootstrapPluginAsset::register($view);
- BootstrapPluginAsset::register($view);
+ $id = $this->options['id'];
- $id = $this->options['id'];
+ if ($this->clientOptions !== false) {
+ $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions);
+ $js = "jQuery('#$id').$name($options);";
+ $view->registerJs($js);
+ }
- if ($this->clientOptions !== false) {
- $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions);
- $js = "jQuery('#$id').$name($options);";
- $view->registerJs($js);
- }
-
- if (!empty($this->clientEvents)) {
- $js = [];
- foreach ($this->clientEvents as $event => $handler) {
- $js[] = "jQuery('#$id').on('$event', $handler);";
- }
- $view->registerJs(implode("\n", $js));
- }
- }
+ if (!empty($this->clientEvents)) {
+ $js = [];
+ foreach ($this->clientEvents as $event => $handler) {
+ $js[] = "jQuery('#$id').on('$event', $handler);";
+ }
+ $view->registerJs(implode("\n", $js));
+ }
+ }
}
diff --git a/extensions/codeception/BasePage.php b/extensions/codeception/BasePage.php
index 145e0c32250..32a12a38d7b 100644
--- a/extensions/codeception/BasePage.php
+++ b/extensions/codeception/BasePage.php
@@ -16,56 +16,58 @@
*/
abstract class BasePage extends Component
{
- /**
- * @var string|array the route (controller ID and action ID, e.g. `site/about`) to this page.
- * Use array to represent a route with GET parameters. The first element of the array represents
- * the route and the rest of the name-value pairs are treated as GET parameters, e.g. `array('site/page', 'name' => 'about')`.
- */
- public $route;
- /**
- * @var \Codeception\AbstractGuy the testing guy object
- */
- protected $guy;
+ /**
+ * @var string|array the route (controller ID and action ID, e.g. `site/about`) to this page.
+ * Use array to represent a route with GET parameters. The first element of the array represents
+ * the route and the rest of the name-value pairs are treated as GET parameters, e.g. `array('site/page', 'name' => 'about')`.
+ */
+ public $route;
+ /**
+ * @var \Codeception\AbstractGuy the testing guy object
+ */
+ protected $guy;
- /**
- * Constructor.
- * @param \Codeception\AbstractGuy the testing guy object
- */
- public function __construct($I)
- {
- $this->guy = $I;
- }
+ /**
+ * Constructor.
+ * @param \Codeception\AbstractGuy the testing guy object
+ */
+ public function __construct($I)
+ {
+ $this->guy = $I;
+ }
- /**
- * Returns the URL to this page.
- * The URL will be returned by calling the URL manager of the application
- * with [[route]] and the provided parameters.
- * @param array $params the GET parameters for creating the URL
- * @return string the URL to this page
- * @throws InvalidConfigException if [[route]] is not set or invalid
- */
- public function getUrl($params = [])
- {
- if (is_string($this->route)) {
- $params[0] = $this->route;
- return Yii::$app->getUrlManager()->createUrl($params);
- } elseif (is_array($this->route) && isset($this->route[0])) {
- return Yii::$app->getUrlManager()->createUrl(array_merge($this->route, $params));
- } else {
- throw new InvalidConfigException('The "route" property must be set.');
- }
- }
+ /**
+ * Returns the URL to this page.
+ * The URL will be returned by calling the URL manager of the application
+ * with [[route]] and the provided parameters.
+ * @param array $params the GET parameters for creating the URL
+ * @return string the URL to this page
+ * @throws InvalidConfigException if [[route]] is not set or invalid
+ */
+ public function getUrl($params = [])
+ {
+ if (is_string($this->route)) {
+ $params[0] = $this->route;
- /**
- * Creates a page instance and sets the test guy to use [[url]].
- * @param \Codeception\AbstractGuy $I the test guy instance
- * @param array $params the GET parameters to be used to generate [[url]]
- * @return static the page instance
- */
- public static function openBy($I, $params = [])
- {
- $page = new static($I);
- $I->amOnPage($page->getUrl($params));
- return $page;
- }
+ return Yii::$app->getUrlManager()->createUrl($params);
+ } elseif (is_array($this->route) && isset($this->route[0])) {
+ return Yii::$app->getUrlManager()->createUrl(array_merge($this->route, $params));
+ } else {
+ throw new InvalidConfigException('The "route" property must be set.');
+ }
+ }
+
+ /**
+ * Creates a page instance and sets the test guy to use [[url]].
+ * @param \Codeception\AbstractGuy $I the test guy instance
+ * @param array $params the GET parameters to be used to generate [[url]]
+ * @return static the page instance
+ */
+ public static function openBy($I, $params = [])
+ {
+ $page = new static($I);
+ $I->amOnPage($page->getUrl($params));
+
+ return $page;
+ }
}
diff --git a/extensions/codeception/DbTestCase.php b/extensions/codeception/DbTestCase.php
index ef6a3790cad..e962e7ab793 100644
--- a/extensions/codeception/DbTestCase.php
+++ b/extensions/codeception/DbTestCase.php
@@ -15,13 +15,13 @@
*/
class DbTestCase extends TestCase
{
- /**
- * @inheritdoc
- */
- public function globalFixtures()
- {
- return [
- InitDbFixture::className(),
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function globalFixtures()
+ {
+ return [
+ InitDbFixture::className(),
+ ];
+ }
}
diff --git a/extensions/codeception/TestCase.php b/extensions/codeception/TestCase.php
index 3ba95265f86..de0f1a7b587 100644
--- a/extensions/codeception/TestCase.php
+++ b/extensions/codeception/TestCase.php
@@ -18,107 +18,108 @@
*/
class TestCase extends Test
{
- use FixtureTrait;
+ use FixtureTrait;
- /**
- * @var array|string the application configuration that will be used for creating an application instance for each test.
- * You can use a string to represent the file path or path alias of a configuration file.
- * The application configuration array may contain an optional `class` element which specifies the class
- * name of the application instance to be created. By default, a [[\yii\web\Application]] instance will be created.
- */
- public $appConfig = '@tests/unit/_config.php';
+ /**
+ * @var array|string the application configuration that will be used for creating an application instance for each test.
+ * You can use a string to represent the file path or path alias of a configuration file.
+ * The application configuration array may contain an optional `class` element which specifies the class
+ * name of the application instance to be created. By default, a [[\yii\web\Application]] instance will be created.
+ */
+ 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);
- }
- }
+ /**
+ * 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()");
- }
- }
+ /**
+ * 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();
- }
+ /**
+ * @inheritdoc
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->mockApplication();
+ $this->unloadFixtures();
+ $this->loadFixtures();
+ }
- /**
- * @inheritdoc
- */
- protected function tearDown()
- {
- $this->destroyApplication();
- parent::tearDown();
- }
+ /**
+ * @inheritdoc
+ */
+ protected function tearDown()
+ {
+ $this->destroyApplication();
+ parent::tearDown();
+ }
- /**
- * Mocks up the application instance.
- * @param array $config the configuration that should be used to generate the application instance.
- * If null, [[appConfig]] will be used.
- * @return \yii\web\Application|\yii\console\Application the application instance
- * @throws InvalidConfigException if the application configuration is invalid
- */
- protected function mockApplication($config = null)
- {
- $config = $config === null ? $this->appConfig : $config;
- if (is_string($config)) {
- $configFile = Yii::getAlias($config);
- if (!is_file($configFile)) {
- throw new InvalidConfigException("The application configuration file does not exist: $config");
- }
- $config = require($configFile);
- }
- if (is_array($config)) {
- if (!isset($config['class'])) {
- $config['class'] = 'yii\web\Application';
- }
- return Yii::createObject($config);
- } else {
- throw new InvalidConfigException('Please provide a configuration array to mock up an application.');
- }
- }
+ /**
+ * Mocks up the application instance.
+ * @param array $config the configuration that should be used to generate the application instance.
+ * If null, [[appConfig]] will be used.
+ * @return \yii\web\Application|\yii\console\Application the application instance
+ * @throws InvalidConfigException if the application configuration is invalid
+ */
+ protected function mockApplication($config = null)
+ {
+ $config = $config === null ? $this->appConfig : $config;
+ if (is_string($config)) {
+ $configFile = Yii::getAlias($config);
+ if (!is_file($configFile)) {
+ throw new InvalidConfigException("The application configuration file does not exist: $config");
+ }
+ $config = require($configFile);
+ }
+ if (is_array($config)) {
+ if (!isset($config['class'])) {
+ $config['class'] = 'yii\web\Application';
+ }
- /**
- * Destroys the application instance created by [[mockApplication]].
- */
- protected function destroyApplication()
- {
- Yii::$app = null;
- }
+ return Yii::createObject($config);
+ } else {
+ throw new InvalidConfigException('Please provide a configuration array to mock up an application.');
+ }
+ }
+
+ /**
+ * Destroys the application instance created by [[mockApplication]].
+ */
+ protected function destroyApplication()
+ {
+ Yii::$app = null;
+ }
}
diff --git a/extensions/composer/Installer.php b/extensions/composer/Installer.php
index 81cc2677282..e597101a4d1 100644
--- a/extensions/composer/Installer.php
+++ b/extensions/composer/Installer.php
@@ -19,181 +19,181 @@
*/
class Installer extends LibraryInstaller
{
- const EXTRA_BOOTSTRAP = 'bootstrap';
- const EXTRA_WRITABLE = 'writable';
- const EXTRA_EXECUTABLE = 'executable';
+ const EXTRA_BOOTSTRAP = 'bootstrap';
+ const EXTRA_WRITABLE = 'writable';
+ const EXTRA_EXECUTABLE = 'executable';
- const EXTENSION_FILE = 'yiisoft/extensions.php';
+ const EXTENSION_FILE = 'yiisoft/extensions.php';
- /**
- * @inheritdoc
- */
- public function supports($packageType)
- {
- return $packageType === 'yii2-extension';
- }
+ /**
+ * @inheritdoc
+ */
+ public function supports($packageType)
+ {
+ return $packageType === 'yii2-extension';
+ }
- /**
- * @inheritdoc
- */
- public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
- {
- // install the package the normal composer way
- parent::install($repo, $package);
- // add the package to yiisoft/extensions.php
- $this->addPackage($package);
- // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
- if ($package->getName() == 'yiisoft/yii2-dev') {
- $this->linkBaseYiiFiles();
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
+ {
+ // install the package the normal composer way
+ parent::install($repo, $package);
+ // add the package to yiisoft/extensions.php
+ $this->addPackage($package);
+ // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
+ if ($package->getName() == 'yiisoft/yii2-dev') {
+ $this->linkBaseYiiFiles();
+ }
+ }
- /**
- * @inheritdoc
- */
- public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
- {
- parent::update($repo, $initial, $target);
- $this->removePackage($initial);
- $this->addPackage($target);
- // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
- if ($initial->getName() == 'yiisoft/yii2-dev') {
- $this->linkBaseYiiFiles();
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
+ {
+ parent::update($repo, $initial, $target);
+ $this->removePackage($initial);
+ $this->addPackage($target);
+ // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
+ if ($initial->getName() == 'yiisoft/yii2-dev') {
+ $this->linkBaseYiiFiles();
+ }
+ }
- /**
- * @inheritdoc
- */
- public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
- {
- // uninstall the package the normal composer way
- parent::uninstall($repo, $package);
- // remove the package from yiisoft/extensions.php
- $this->removePackage($package);
- // remove links for Yii.php
- if ($package->getName() == 'yiisoft/yii2-dev') {
- $this->removeBaseYiiFiles();
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
+ {
+ // uninstall the package the normal composer way
+ parent::uninstall($repo, $package);
+ // remove the package from yiisoft/extensions.php
+ $this->removePackage($package);
+ // remove links for Yii.php
+ if ($package->getName() == 'yiisoft/yii2-dev') {
+ $this->removeBaseYiiFiles();
+ }
+ }
- protected function addPackage(PackageInterface $package)
- {
- $extension = [
- 'name' => $package->getName(),
- 'version' => $package->getVersion(),
- ];
+ protected function addPackage(PackageInterface $package)
+ {
+ $extension = [
+ 'name' => $package->getName(),
+ 'version' => $package->getVersion(),
+ ];
- $alias = $this->generateDefaultAlias($package);
- if (!empty($alias)) {
- $extension['alias'] = $alias;
- }
- $extra = $package->getExtra();
- if (isset($extra[self::EXTRA_BOOTSTRAP]) && is_string($extra[self::EXTRA_BOOTSTRAP])) {
- $extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP];
- }
+ $alias = $this->generateDefaultAlias($package);
+ if (!empty($alias)) {
+ $extension['alias'] = $alias;
+ }
+ $extra = $package->getExtra();
+ if (isset($extra[self::EXTRA_BOOTSTRAP]) && is_string($extra[self::EXTRA_BOOTSTRAP])) {
+ $extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP];
+ }
- $extensions = $this->loadExtensions();
- $extensions[$package->getName()] = $extension;
- $this->saveExtensions($extensions);
- }
+ $extensions = $this->loadExtensions();
+ $extensions[$package->getName()] = $extension;
+ $this->saveExtensions($extensions);
+ }
- protected function generateDefaultAlias(PackageInterface $package)
- {
- $fs = new Filesystem;
- $vendorDir = $fs->normalizePath($this->vendorDir);
- $autoload = $package->getAutoload();
+ protected function generateDefaultAlias(PackageInterface $package)
+ {
+ $fs = new Filesystem;
+ $vendorDir = $fs->normalizePath($this->vendorDir);
+ $autoload = $package->getAutoload();
- $aliases = [];
+ $aliases = [];
- if (!empty($autoload['psr-0'])) {
- foreach ($autoload['psr-0'] as $name => $path) {
- $name = str_replace('\\', '/', trim($name, '\\'));
- if (!$fs->isAbsolutePath($path)) {
- $path = $this->vendorDir . '/' . $package->getName() . '/' . $path;
- }
- $path = $fs->normalizePath($path);
- if (strpos($path . '/', $vendorDir . '/') === 0) {
- $aliases["@$name"] = '' . substr($path, strlen($vendorDir)) . '/' . $name;
- } else {
- $aliases["@$name"] = $path . '/' . $name;
- }
- }
- }
+ if (!empty($autoload['psr-0'])) {
+ foreach ($autoload['psr-0'] as $name => $path) {
+ $name = str_replace('\\', '/', trim($name, '\\'));
+ if (!$fs->isAbsolutePath($path)) {
+ $path = $this->vendorDir . '/' . $package->getName() . '/' . $path;
+ }
+ $path = $fs->normalizePath($path);
+ if (strpos($path . '/', $vendorDir . '/') === 0) {
+ $aliases["@$name"] = '' . substr($path, strlen($vendorDir)) . '/' . $name;
+ } else {
+ $aliases["@$name"] = $path . '/' . $name;
+ }
+ }
+ }
- if (!empty($autoload['psr-4'])) {
- foreach ($autoload['psr-4'] as $name => $path) {
- $name = str_replace('\\', '/', trim($name, '\\'));
- if (!$fs->isAbsolutePath($path)) {
- $path = $this->vendorDir . '/' . $package->getName() . '/' . $path;
- }
- $path = $fs->normalizePath($path);
- if (strpos($path . '/', $vendorDir . '/') === 0) {
- $aliases["@$name"] = '' . substr($path, strlen($vendorDir));
- } else {
- $aliases["@$name"] = $path;
- }
- }
- }
+ if (!empty($autoload['psr-4'])) {
+ foreach ($autoload['psr-4'] as $name => $path) {
+ $name = str_replace('\\', '/', trim($name, '\\'));
+ if (!$fs->isAbsolutePath($path)) {
+ $path = $this->vendorDir . '/' . $package->getName() . '/' . $path;
+ }
+ $path = $fs->normalizePath($path);
+ if (strpos($path . '/', $vendorDir . '/') === 0) {
+ $aliases["@$name"] = '' . substr($path, strlen($vendorDir));
+ } else {
+ $aliases["@$name"] = $path;
+ }
+ }
+ }
- return $aliases;
- }
+ return $aliases;
+ }
- protected function removePackage(PackageInterface $package)
- {
- $packages = $this->loadExtensions();
- unset($packages[$package->getName()]);
- $this->saveExtensions($packages);
- }
+ protected function removePackage(PackageInterface $package)
+ {
+ $packages = $this->loadExtensions();
+ unset($packages[$package->getName()]);
+ $this->saveExtensions($packages);
+ }
- protected function loadExtensions()
- {
- $file = $this->vendorDir . '/' . self::EXTENSION_FILE;
- if (!is_file($file)) {
- return [];
- }
- // invalidate opcache of extensions.php if exists
- if (function_exists('opcache_invalidate')) {
- opcache_invalidate($file, true);
- }
- $extensions = require($file);
+ protected function loadExtensions()
+ {
+ $file = $this->vendorDir . '/' . self::EXTENSION_FILE;
+ if (!is_file($file)) {
+ return [];
+ }
+ // invalidate opcache of extensions.php if exists
+ if (function_exists('opcache_invalidate')) {
+ opcache_invalidate($file, true);
+ }
+ $extensions = require($file);
- $vendorDir = str_replace('\\', '/', $this->vendorDir);
- $n = strlen($vendorDir);
+ $vendorDir = str_replace('\\', '/', $this->vendorDir);
+ $n = strlen($vendorDir);
- foreach ($extensions as &$extension) {
- if (isset($extension['alias'])) {
- foreach ($extension['alias'] as $alias => $path) {
- $path = str_replace('\\', '/', $path);
- if (strpos($path . '/', $vendorDir . '/') === 0) {
- $extension['alias'][$alias] = '' . substr($path, $n);
- }
- }
- }
- }
+ foreach ($extensions as &$extension) {
+ if (isset($extension['alias'])) {
+ foreach ($extension['alias'] as $alias => $path) {
+ $path = str_replace('\\', '/', $path);
+ if (strpos($path . '/', $vendorDir . '/') === 0) {
+ $extension['alias'][$alias] = '' . substr($path, $n);
+ }
+ }
+ }
+ }
- return $extensions;
- }
+ return $extensions;
+ }
- protected function saveExtensions(array $extensions)
- {
- $file = $this->vendorDir . '/' . self::EXTENSION_FILE;
- $array = str_replace("'", '$vendorDir . \'', var_export($extensions, true));
- file_put_contents($file, "vendorDir . '/' . self::EXTENSION_FILE;
+ $array = str_replace("'", '$vendorDir . \'', var_export($extensions, true));
+ file_put_contents($file, "vendorDir . '/yiisoft/yii2';
- if (!file_exists($yiiDir)) {
- mkdir($yiiDir, 0777, true);
- }
- foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
- file_put_contents($yiiDir . '/' . $file, <<vendorDir . '/yiisoft/yii2';
+ if (!file_exists($yiiDir)) {
+ mkdir($yiiDir, 0777, true);
+ }
+ foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
+ file_put_contents($yiiDir . '/' . $file, <<vendorDir . '/yiisoft/yii2';
- foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
- if (file_exists($yiiDir . '/' . $file)) {
- unlink($yiiDir . '/' . $file);
- }
- }
- if (file_exists($yiiDir)) {
- rmdir($yiiDir);
- }
- }
+ protected function removeBaseYiiFiles()
+ {
+ $yiiDir = $this->vendorDir . '/yiisoft/yii2';
+ foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
+ if (file_exists($yiiDir . '/' . $file)) {
+ unlink($yiiDir . '/' . $file);
+ }
+ }
+ if (file_exists($yiiDir)) {
+ rmdir($yiiDir);
+ }
+ }
- /**
- * Sets the correct permission for the files and directories listed in the extra section.
- * @param CommandEvent $event
- */
- public static function setPermission($event)
- {
- $options = array_merge([
- self::EXTRA_WRITABLE => [],
- self::EXTRA_EXECUTABLE => [],
- ], $event->getComposer()->getPackage()->getExtra());
+ /**
+ * Sets the correct permission for the files and directories listed in the extra section.
+ * @param CommandEvent $event
+ */
+ public static function setPermission($event)
+ {
+ $options = array_merge([
+ self::EXTRA_WRITABLE => [],
+ self::EXTRA_EXECUTABLE => [],
+ ], $event->getComposer()->getPackage()->getExtra());
- foreach ((array)$options[self::EXTRA_WRITABLE] as $path) {
- echo "Setting writable: $path ...";
- if (is_dir($path)) {
- chmod($path, 0777);
- echo "done\n";
- } else {
- echo "The directory was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path;
- return;
- }
- }
+ foreach ((array) $options[self::EXTRA_WRITABLE] as $path) {
+ echo "Setting writable: $path ...";
+ if (is_dir($path)) {
+ chmod($path, 0777);
+ echo "done\n";
+ } else {
+ echo "The directory was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path;
- foreach ((array)$options[self::EXTRA_EXECUTABLE] as $path) {
- echo "Setting executable: $path ...";
- if (is_file($path)) {
- chmod($path, 0755);
- echo "done\n";
- } else {
- echo "\n\tThe file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n";
- return;
- }
- }
- }
+ return;
+ }
+ }
+
+ foreach ((array) $options[self::EXTRA_EXECUTABLE] as $path) {
+ echo "Setting executable: $path ...";
+ if (is_file($path)) {
+ chmod($path, 0755);
+ echo "done\n";
+ } else {
+ echo "\n\tThe file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n";
+
+ return;
+ }
+ }
+ }
}
diff --git a/extensions/composer/Plugin.php b/extensions/composer/Plugin.php
index 1111738e32a..1f08d53f6b3 100644
--- a/extensions/composer/Plugin.php
+++ b/extensions/composer/Plugin.php
@@ -19,17 +19,17 @@
*/
class Plugin implements PluginInterface
{
- /**
- * @inheritdoc
- */
- public function activate(Composer $composer, IOInterface $io)
- {
- $installer = new Installer($io, $composer);
- $composer->getInstallationManager()->addInstaller($installer);
- $file = rtrim($composer->getConfig()->get('vendor-dir'), '/') . '/yiisoft/extensions.php';
- if (!is_file($file)) {
- @mkdir(dirname($file));
- file_put_contents($file, "getInstallationManager()->addInstaller($installer);
+ $file = rtrim($composer->getConfig()->get('vendor-dir'), '/') . '/yiisoft/extensions.php';
+ if (!is_file($file)) {
+ @mkdir(dirname($file));
+ file_put_contents($file, "module = $module;
- $this->tag = uniqid();
- }
-
- /**
- * Exports log messages to a specific destination.
- * Child classes must implement this method.
- */
- public function export()
- {
- $path = $this->module->dataPath;
- if (!is_dir($path)) {
- mkdir($path);
- }
-
- $summary = $this->collectSummary();
- $dataFile = "$path/{$this->tag}.data";
- $data = [];
- foreach ($this->module->panels as $id => $panel) {
- $data[$id] = $panel->save();
- }
- $data['summary'] = $summary;
- file_put_contents($dataFile, serialize($data));
-
- $indexFile = "$path/index.data";
- $this->updateIndexFile($indexFile, $summary);
- }
-
- /**
- * Updates index file with summary log data
- *
- * @param string $indexFile path to index file
- * @param array $summary summary log data
- * @throws \yii\base\InvalidConfigException
- */
- private function updateIndexFile($indexFile, $summary)
- {
- touch($indexFile);
- if (($fp = @fopen($indexFile, 'r+')) === false) {
- throw new InvalidConfigException("Unable to open debug data index file: $indexFile");
- }
- @flock($fp, LOCK_EX);
- $manifest = '';
- while (($buffer = fgets($fp)) !== false) {
- $manifest .= $buffer;
- }
- if (!feof($fp) || empty($manifest)) {
- // error while reading index data, ignore and create new
- $manifest = [];
- } else {
- $manifest = unserialize($manifest);
- }
-
- $manifest[$this->tag] = $summary;
- $this->gc($manifest);
-
- ftruncate($fp, 0);
- rewind($fp);
- fwrite($fp, serialize($manifest));
-
- @flock($fp, LOCK_UN);
- @fclose($fp);
- }
-
- /**
- * Processes the given log messages.
- * This method will filter the given messages with [[levels]] and [[categories]].
- * And if requested, it will also export the filtering result to specific medium (e.g. email).
- * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
- * of each message.
- * @param boolean $final whether this method is called at the end of the current application
- */
- public function collect($messages, $final)
- {
- $this->messages = array_merge($this->messages, $messages);
- if ($final) {
- $this->export($this->messages);
- }
- }
-
- protected function gc(&$manifest)
- {
- if (count($manifest) > $this->module->historySize + 10) {
- $n = count($manifest) - $this->module->historySize;
- foreach (array_keys($manifest) as $tag) {
- $file = $this->module->dataPath . "/$tag.data";
- @unlink($file);
- unset($manifest[$tag]);
- if (--$n <= 0) {
- break;
- }
- }
- }
- }
-
- /**
- * Collects summary data of current request.
- * @return array
- */
- protected function collectSummary()
- {
- $request = Yii::$app->getRequest();
- $response = Yii::$app->getResponse();
- $summary = [
- 'tag' => $this->tag,
- 'url' => $request->getAbsoluteUrl(),
- 'ajax' => $request->getIsAjax(),
- 'method' => $request->getMethod(),
- 'ip' => $request->getUserIP(),
- 'time' => time(),
- 'statusCode' => $response->statusCode,
- 'sqlCount' => $this->getSqlTotalCount(),
- ];
-
- if (isset($this->module->panels['mail'])) {
- $summary['mailCount'] = count($this->module->panels['mail']->getMessages());
- }
-
- return $summary;
- }
-
- /**
- * Returns total sql count executed in current request. If database panel is not configured
- * returns 0.
- * @return integer
- */
- protected function getSqlTotalCount()
- {
- if (!isset($this->module->panels['db'])) {
- return 0;
- }
- $profileLogs = $this->module->panels['db']->getProfileLogs();
-
- # / 2 because messages are in couple (begin/end)
- return count($profileLogs) / 2;
- }
+ /**
+ * @var Module
+ */
+ public $module;
+ public $tag;
+
+ /**
+ * @param \yii\debug\Module $module
+ * @param array $config
+ */
+ public function __construct($module, $config = [])
+ {
+ parent::__construct($config);
+ $this->module = $module;
+ $this->tag = uniqid();
+ }
+
+ /**
+ * Exports log messages to a specific destination.
+ * Child classes must implement this method.
+ */
+ public function export()
+ {
+ $path = $this->module->dataPath;
+ if (!is_dir($path)) {
+ mkdir($path);
+ }
+
+ $summary = $this->collectSummary();
+ $dataFile = "$path/{$this->tag}.data";
+ $data = [];
+ foreach ($this->module->panels as $id => $panel) {
+ $data[$id] = $panel->save();
+ }
+ $data['summary'] = $summary;
+ file_put_contents($dataFile, serialize($data));
+
+ $indexFile = "$path/index.data";
+ $this->updateIndexFile($indexFile, $summary);
+ }
+
+ /**
+ * Updates index file with summary log data
+ *
+ * @param string $indexFile path to index file
+ * @param array $summary summary log data
+ * @throws \yii\base\InvalidConfigException
+ */
+ private function updateIndexFile($indexFile, $summary)
+ {
+ touch($indexFile);
+ if (($fp = @fopen($indexFile, 'r+')) === false) {
+ throw new InvalidConfigException("Unable to open debug data index file: $indexFile");
+ }
+ @flock($fp, LOCK_EX);
+ $manifest = '';
+ while (($buffer = fgets($fp)) !== false) {
+ $manifest .= $buffer;
+ }
+ if (!feof($fp) || empty($manifest)) {
+ // error while reading index data, ignore and create new
+ $manifest = [];
+ } else {
+ $manifest = unserialize($manifest);
+ }
+
+ $manifest[$this->tag] = $summary;
+ $this->gc($manifest);
+
+ ftruncate($fp, 0);
+ rewind($fp);
+ fwrite($fp, serialize($manifest));
+
+ @flock($fp, LOCK_UN);
+ @fclose($fp);
+ }
+
+ /**
+ * Processes the given log messages.
+ * This method will filter the given messages with [[levels]] and [[categories]].
+ * And if requested, it will also export the filtering result to specific medium (e.g. email).
+ * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
+ * of each message.
+ * @param boolean $final whether this method is called at the end of the current application
+ */
+ public function collect($messages, $final)
+ {
+ $this->messages = array_merge($this->messages, $messages);
+ if ($final) {
+ $this->export($this->messages);
+ }
+ }
+
+ protected function gc(&$manifest)
+ {
+ if (count($manifest) > $this->module->historySize + 10) {
+ $n = count($manifest) - $this->module->historySize;
+ foreach (array_keys($manifest) as $tag) {
+ $file = $this->module->dataPath . "/$tag.data";
+ @unlink($file);
+ unset($manifest[$tag]);
+ if (--$n <= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Collects summary data of current request.
+ * @return array
+ */
+ protected function collectSummary()
+ {
+ $request = Yii::$app->getRequest();
+ $response = Yii::$app->getResponse();
+ $summary = [
+ 'tag' => $this->tag,
+ 'url' => $request->getAbsoluteUrl(),
+ 'ajax' => $request->getIsAjax(),
+ 'method' => $request->getMethod(),
+ 'ip' => $request->getUserIP(),
+ 'time' => time(),
+ 'statusCode' => $response->statusCode,
+ 'sqlCount' => $this->getSqlTotalCount(),
+ ];
+
+ if (isset($this->module->panels['mail'])) {
+ $summary['mailCount'] = count($this->module->panels['mail']->getMessages());
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Returns total sql count executed in current request. If database panel is not configured
+ * returns 0.
+ * @return integer
+ */
+ protected function getSqlTotalCount()
+ {
+ if (!isset($this->module->panels['db'])) {
+ return 0;
+ }
+ $profileLogs = $this->module->panels['db']->getProfileLogs();
+
+ # / 2 because messages are in couple (begin/end)
+
+ return count($profileLogs) / 2;
+ }
}
diff --git a/extensions/debug/Module.php b/extensions/debug/Module.php
index b6d62304cb7..a2c534c8561 100644
--- a/extensions/debug/Module.php
+++ b/extensions/debug/Module.php
@@ -20,148 +20,149 @@
*/
class Module extends \yii\base\Module
{
- /**
- * @var array the list of IPs that are allowed to access this module.
- * Each array element represents a single IP filter which can be either an IP address
- * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment.
- * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed
- * by localhost.
- */
- public $allowedIPs = ['127.0.0.1', '::1'];
- /**
- * @var string the namespace that controller classes are in.
- */
- public $controllerNamespace = 'yii\debug\controllers';
- /**
- * @var LogTarget
- */
- public $logTarget;
- /**
- * @var array list of debug panels. The array keys are the panel IDs, and values are the corresponding
- * panel class names or configuration arrays. This will be merged with [[corePanels()]].
- * You may reconfigure a core panel via this property by using the same panel ID.
- * You may also disable a core panel by setting it to be false in this property.
- */
- public $panels = [];
- /**
- * @var string the directory storing the debugger data files. This can be specified using a path alias.
- */
- public $dataPath = '@runtime/debug';
- /**
- * @var integer the maximum number of debug data files to keep. If there are more files generated,
- * the oldest ones will be removed.
- */
- public $historySize = 50;
+ /**
+ * @var array the list of IPs that are allowed to access this module.
+ * Each array element represents a single IP filter which can be either an IP address
+ * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment.
+ * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed
+ * by localhost.
+ */
+ public $allowedIPs = ['127.0.0.1', '::1'];
+ /**
+ * @var string the namespace that controller classes are in.
+ */
+ public $controllerNamespace = 'yii\debug\controllers';
+ /**
+ * @var LogTarget
+ */
+ public $logTarget;
+ /**
+ * @var array list of debug panels. The array keys are the panel IDs, and values are the corresponding
+ * panel class names or configuration arrays. This will be merged with [[corePanels()]].
+ * You may reconfigure a core panel via this property by using the same panel ID.
+ * You may also disable a core panel by setting it to be false in this property.
+ */
+ public $panels = [];
+ /**
+ * @var string the directory storing the debugger data files. This can be specified using a path alias.
+ */
+ public $dataPath = '@runtime/debug';
+ /**
+ * @var integer the maximum number of debug data files to keep. If there are more files generated,
+ * the oldest ones will be removed.
+ */
+ public $historySize = 50;
- /**
- * Returns Yii logo ready to use in `dataPath = Yii::getAlias($this->dataPath);
- $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this);
- // do not initialize view component before application is ready (needed when debug in preload)
- Yii::$app->on(Application::EVENT_BEFORE_REQUEST, function () {
- Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
- });
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ $this->dataPath = Yii::getAlias($this->dataPath);
+ $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this);
+ // do not initialize view component before application is ready (needed when debug in preload)
+ Yii::$app->on(Application::EVENT_BEFORE_REQUEST, function () {
+ Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
+ });
- // merge custom panels and core panels so that they are ordered mainly by custom panels
- if (empty($this->panels)) {
- $this->panels = $this->corePanels();
- } else {
- $corePanels = $this->corePanels();
- foreach ($corePanels as $id => $config) {
- if (isset($this->panels[$id])) {
- unset($corePanels[$id]);
- }
- }
- $this->panels = array_filter(array_merge($corePanels, $this->panels));
- }
+ // merge custom panels and core panels so that they are ordered mainly by custom panels
+ if (empty($this->panels)) {
+ $this->panels = $this->corePanels();
+ } else {
+ $corePanels = $this->corePanels();
+ foreach ($corePanels as $id => $config) {
+ if (isset($this->panels[$id])) {
+ unset($corePanels[$id]);
+ }
+ }
+ $this->panels = array_filter(array_merge($corePanels, $this->panels));
+ }
- foreach ($this->panels as $id => $config) {
- $config['module'] = $this;
- $config['id'] = $id;
- $this->panels[$id] = Yii::createObject($config);
- }
- }
+ foreach ($this->panels as $id => $config) {
+ $config['module'] = $this;
+ $config['id'] = $id;
+ $this->panels[$id] = Yii::createObject($config);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function beforeAction($action)
- {
- Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']);
- unset(Yii::$app->getLog()->targets['debug']);
- $this->logTarget = null;
+ /**
+ * @inheritdoc
+ */
+ public function beforeAction($action)
+ {
+ Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']);
+ unset(Yii::$app->getLog()->targets['debug']);
+ $this->logTarget = null;
- if ($this->checkAccess()) {
- return parent::beforeAction($action);
- } elseif ($action->id === 'toolbar') {
- return false;
- } else {
- throw new ForbiddenHttpException('You are not allowed to access this page.');
- }
- }
+ if ($this->checkAccess()) {
+ return parent::beforeAction($action);
+ } elseif ($action->id === 'toolbar') {
+ return false;
+ } else {
+ throw new ForbiddenHttpException('You are not allowed to access this page.');
+ }
+ }
- /**
- * Renders mini-toolbar at the end of page body.
- *
- * @param \yii\base\Event $event
- */
- public function renderToolbar($event)
- {
- if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) {
- return;
- }
- $url = Yii::$app->getUrlManager()->createUrl([$this->id . '/default/toolbar',
- 'tag' => $this->logTarget->tag,
- ]);
- echo '';
- /** @var View $view */
- $view = $event->sender;
- echo '';
- echo '';
- }
+ /**
+ * Renders mini-toolbar at the end of page body.
+ *
+ * @param \yii\base\Event $event
+ */
+ public function renderToolbar($event)
+ {
+ if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) {
+ return;
+ }
+ $url = Yii::$app->getUrlManager()->createUrl([$this->id . '/default/toolbar',
+ 'tag' => $this->logTarget->tag,
+ ]);
+ echo '';
+ /** @var View $view */
+ $view = $event->sender;
+ echo '';
+ echo '';
+ }
- /**
- * Checks if current user is allowed to access the module
- * @return boolean if access is granted
- */
- protected function checkAccess()
- {
- $ip = Yii::$app->getRequest()->getUserIP();
- foreach ($this->allowedIPs as $filter) {
- if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) {
- return true;
- }
- }
- Yii::warning('Access to debugger is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__);
- return false;
- }
+ /**
+ * Checks if current user is allowed to access the module
+ * @return boolean if access is granted
+ */
+ protected function checkAccess()
+ {
+ $ip = Yii::$app->getRequest()->getUserIP();
+ foreach ($this->allowedIPs as $filter) {
+ if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) {
+ return true;
+ }
+ }
+ Yii::warning('Access to debugger is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__);
- /**
- * @return array default set of panels
- */
- protected function corePanels()
- {
- return [
- 'config' => ['class' => 'yii\debug\panels\ConfigPanel'],
- 'request' => ['class' => 'yii\debug\panels\RequestPanel'],
- 'log' => ['class' => 'yii\debug\panels\LogPanel'],
- 'profiling' => ['class' => 'yii\debug\panels\ProfilingPanel'],
- 'db' => ['class' => 'yii\debug\panels\DbPanel'],
- 'mail' => ['class' => 'yii\debug\panels\MailPanel'],
- ];
- }
+ return false;
+ }
+
+ /**
+ * @return array default set of panels
+ */
+ protected function corePanels()
+ {
+ return [
+ 'config' => ['class' => 'yii\debug\panels\ConfigPanel'],
+ 'request' => ['class' => 'yii\debug\panels\RequestPanel'],
+ 'log' => ['class' => 'yii\debug\panels\LogPanel'],
+ 'profiling' => ['class' => 'yii\debug\panels\ProfilingPanel'],
+ 'db' => ['class' => 'yii\debug\panels\DbPanel'],
+ 'mail' => ['class' => 'yii\debug\panels\MailPanel'],
+ ];
+ }
}
diff --git a/extensions/debug/Panel.php b/extensions/debug/Panel.php
index 0f9e673c9d5..2e4b2b346b9 100644
--- a/extensions/debug/Panel.php
+++ b/extensions/debug/Panel.php
@@ -24,73 +24,73 @@
*/
class Panel extends Component
{
- public $id;
- public $tag;
- /**
- * @var Module
- */
- public $module;
- public $data;
- /**
- * @var array array of actions to add to the debug modules default controller.
- * This array will be merged with all other panels actions property.
- * See [[\yii\base\Controller::actions()]] for the format.
- */
- public $actions = [];
+ public $id;
+ public $tag;
+ /**
+ * @var Module
+ */
+ public $module;
+ public $data;
+ /**
+ * @var array array of actions to add to the debug modules default controller.
+ * This array will be merged with all other panels actions property.
+ * See [[\yii\base\Controller::actions()]] for the format.
+ */
+ public $actions = [];
- /**
- * @return string name of the panel
- */
- public function getName()
- {
- return '';
- }
+ /**
+ * @return string name of the panel
+ */
+ public function getName()
+ {
+ return '';
+ }
- /**
- * @return string content that is displayed at debug toolbar
- */
- public function getSummary()
- {
- return '';
- }
+ /**
+ * @return string content that is displayed at debug toolbar
+ */
+ public function getSummary()
+ {
+ return '';
+ }
- /**
- * @return string content that is displayed in debugger detail view
- */
- public function getDetail()
- {
- return '';
- }
+ /**
+ * @return string content that is displayed in debugger detail view
+ */
+ public function getDetail()
+ {
+ return '';
+ }
- /**
- * Saves data to be later used in debugger detail view.
- * This method is called on every page where debugger is enabled.
- *
- * @return mixed data to be saved
- */
- public function save()
- {
- return null;
- }
+ /**
+ * Saves data to be later used in debugger detail view.
+ * This method is called on every page where debugger is enabled.
+ *
+ * @return mixed data to be saved
+ */
+ public function save()
+ {
+ return null;
+ }
- /**
- * Loads data into the panel
- *
- * @param mixed $data
- */
- public function load($data)
- {
- $this->data = $data;
- }
+ /**
+ * Loads data into the panel
+ *
+ * @param mixed $data
+ */
+ public function load($data)
+ {
+ $this->data = $data;
+ }
- /**
- * @return string URL pointing to panel detail view
- */
- public function getUrl()
- {
- return Yii::$app->getUrlManager()->createUrl([$this->module->id . '/default/view',
- 'panel' => $this->id,
- 'tag' => $this->tag,
- ]);
- }
+ /**
+ * @return string URL pointing to panel detail view
+ */
+ public function getUrl()
+ {
+ return Yii::$app->getUrlManager()->createUrl([$this->module->id . '/default/view',
+ 'panel' => $this->id,
+ 'tag' => $this->tag,
+ ]);
+ }
}
diff --git a/extensions/debug/components/search/Filter.php b/extensions/debug/components/search/Filter.php
index 55e6ebb3acf..09830824c18 100644
--- a/extensions/debug/components/search/Filter.php
+++ b/extensions/debug/components/search/Filter.php
@@ -18,63 +18,63 @@
*/
class Filter extends Component
{
- /**
- * @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]]
- */
- protected $rules = [];
+ /**
+ * @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]]
+ */
+ protected $rules = [];
- /**
- * Adds data filtering rule.
- *
- * @param string $name attribute name
- * @param MatcherInterface $rule
- */
- public function addMatcher($name, MatcherInterface $rule)
- {
- if ($rule->hasValue()) {
- $this->rules[$name][] = $rule;
- }
- }
+ /**
+ * Adds data filtering rule.
+ *
+ * @param string $name attribute name
+ * @param MatcherInterface $rule
+ */
+ public function addMatcher($name, MatcherInterface $rule)
+ {
+ if ($rule->hasValue()) {
+ $this->rules[$name][] = $rule;
+ }
+ }
- /**
- * Applies filter on a given array and returns filtered data.
- *
- * @param array $data data to filter
- * @return array filtered data
- */
- public function filter(array $data)
- {
- $filtered = [];
+ /**
+ * Applies filter on a given array and returns filtered data.
+ *
+ * @param array $data data to filter
+ * @return array filtered data
+ */
+ public function filter(array $data)
+ {
+ $filtered = [];
- foreach ($data as $row) {
- if ($this->passesFilter($row)) {
- $filtered[] = $row;
- }
- }
+ foreach ($data as $row) {
+ if ($this->passesFilter($row)) {
+ $filtered[] = $row;
+ }
+ }
- return $filtered;
- }
+ return $filtered;
+ }
- /**
- * Checks if the given data satisfies filters.
- *
- * @param array $row data
- * @return boolean if data passed filtering
- */
- private function passesFilter(array $row)
- {
- foreach ($row as $name => $value) {
- if (isset($this->rules[$name])) {
- // check all rules for a given attribute
- foreach ($this->rules[$name] as $rule) {
- /** @var MatcherInterface $rule */
- if (!$rule->match($value)) {
- return false;
- }
- }
- }
- }
+ /**
+ * Checks if the given data satisfies filters.
+ *
+ * @param array $row data
+ * @return boolean if data passed filtering
+ */
+ private function passesFilter(array $row)
+ {
+ foreach ($row as $name => $value) {
+ if (isset($this->rules[$name])) {
+ // check all rules for a given attribute
+ foreach ($this->rules[$name] as $rule) {
+ /** @var MatcherInterface $rule */
+ if (!$rule->match($value)) {
+ return false;
+ }
+ }
+ }
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/extensions/debug/components/search/matchers/Base.php b/extensions/debug/components/search/matchers/Base.php
index a29b40b2862..e98a7242243 100644
--- a/extensions/debug/components/search/matchers/Base.php
+++ b/extensions/debug/components/search/matchers/Base.php
@@ -17,24 +17,24 @@
*/
abstract class Base extends Component implements MatcherInterface
{
- /**
- * @var mixed base value to check
- */
- protected $baseValue;
+ /**
+ * @var mixed base value to check
+ */
+ protected $baseValue;
- /**
- * @inheritdoc
- */
- public function setValue($value)
- {
- $this->baseValue = $value;
- }
+ /**
+ * @inheritdoc
+ */
+ public function setValue($value)
+ {
+ $this->baseValue = $value;
+ }
- /**
- * @inheritdoc
- */
- public function hasValue()
- {
- return !empty($this->baseValue) || ($this->baseValue === '0');
- }
+ /**
+ * @inheritdoc
+ */
+ public function hasValue()
+ {
+ return !empty($this->baseValue) || ($this->baseValue === '0');
+ }
}
diff --git a/extensions/debug/components/search/matchers/GreaterThan.php b/extensions/debug/components/search/matchers/GreaterThan.php
index 486fac462f2..abc907baabf 100644
--- a/extensions/debug/components/search/matchers/GreaterThan.php
+++ b/extensions/debug/components/search/matchers/GreaterThan.php
@@ -15,11 +15,11 @@
*/
class GreaterThan extends Base
{
- /**
- * @inheritdoc
- */
- public function match($value)
- {
- return ($value > $this->baseValue);
- }
+ /**
+ * @inheritdoc
+ */
+ public function match($value)
+ {
+ return ($value > $this->baseValue);
+ }
}
diff --git a/extensions/debug/components/search/matchers/LowerThan.php b/extensions/debug/components/search/matchers/LowerThan.php
index 018001a0a2c..c3d4f32b8e8 100644
--- a/extensions/debug/components/search/matchers/LowerThan.php
+++ b/extensions/debug/components/search/matchers/LowerThan.php
@@ -15,11 +15,11 @@
*/
class LowerThan extends Base
{
- /**
- * @inheritdoc
- */
- public function match($value)
- {
- return ($value < $this->baseValue);
- }
+ /**
+ * @inheritdoc
+ */
+ public function match($value)
+ {
+ return ($value < $this->baseValue);
+ }
}
diff --git a/extensions/debug/components/search/matchers/MatcherInterface.php b/extensions/debug/components/search/matchers/MatcherInterface.php
index febd06d68dd..c601bbc0029 100644
--- a/extensions/debug/components/search/matchers/MatcherInterface.php
+++ b/extensions/debug/components/search/matchers/MatcherInterface.php
@@ -9,31 +9,31 @@
/**
* MatcherInterface should be implemented by all matchers that are used in a filter.
- *
+ *
* @author Mark Jebri
* @since 2.0
*/
interface MatcherInterface
{
- /**
- * Checks if the value passed matches base value.
- *
- * @param mixed $value value to be matched
- * @return boolean if there is a match
- */
- public function match($value);
+ /**
+ * Checks if the value passed matches base value.
+ *
+ * @param mixed $value value to be matched
+ * @return boolean if there is a match
+ */
+ public function match($value);
- /**
- * Sets base value to match against
- *
- * @param mixed $value
- */
- public function setValue($value);
+ /**
+ * Sets base value to match against
+ *
+ * @param mixed $value
+ */
+ public function setValue($value);
- /**
- * Checks if base value is set
- *
- * @return boolean if base value is set
- */
- public function hasValue();
+ /**
+ * Checks if base value is set
+ *
+ * @return boolean if base value is set
+ */
+ public function hasValue();
}
diff --git a/extensions/debug/components/search/matchers/SameAs.php b/extensions/debug/components/search/matchers/SameAs.php
index ba3eedd7101..bb3088d187c 100644
--- a/extensions/debug/components/search/matchers/SameAs.php
+++ b/extensions/debug/components/search/matchers/SameAs.php
@@ -15,20 +15,20 @@
*/
class SameAs extends Base
{
- /**
- * @var boolean if partial match should be used.
- */
- public $partial = false;
+ /**
+ * @var boolean if partial match should be used.
+ */
+ public $partial = false;
- /**
- * @inheritdoc
- */
- public function match($value)
- {
- if (!$this->partial) {
- return (mb_strtolower($this->baseValue, 'utf8') == mb_strtolower($value, 'utf8'));
- } else {
- return (mb_strpos(mb_strtolower($value, 'utf8'), mb_strtolower($this->baseValue, 'utf8')) !== false);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function match($value)
+ {
+ if (!$this->partial) {
+ return (mb_strtolower($this->baseValue, 'utf8') == mb_strtolower($value, 'utf8'));
+ } else {
+ return (mb_strpos(mb_strtolower($value, 'utf8'), mb_strtolower($this->baseValue, 'utf8')) !== false);
+ }
+ }
}
diff --git a/extensions/debug/controllers/DefaultController.php b/extensions/debug/controllers/DefaultController.php
index 9f64d40bc6c..ca227696770 100644
--- a/extensions/debug/controllers/DefaultController.php
+++ b/extensions/debug/controllers/DefaultController.php
@@ -20,133 +20,138 @@
*/
class DefaultController extends Controller
{
- /**
- * @inheritdoc
- */
- public $layout = 'main';
- /**
- * @var \yii\debug\Module
- */
- public $module;
- /**
- * @var array the summary data (e.g. URL, time)
- */
- public $summary;
-
- /**
- * @inheritdoc
- */
- public function actions()
- {
- $actions = [];
- foreach ($this->module->panels as $panel) {
- $actions = array_merge($actions, $panel->actions);
- }
- return $actions;
- }
-
- public function actionIndex()
- {
- $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,
- ]);
- }
-
- public function actionView($tag = null, $panel = null)
- {
- if ($tag === null) {
- $tags = array_keys($this->getManifest());
- $tag = reset($tags);
- }
- $this->loadData($tag);
- if (isset($this->module->panels[$panel])) {
- $activePanel = $this->module->panels[$panel];
- } else {
- $activePanel = $this->module->panels['request'];
- }
- return $this->render('view', [
- 'tag' => $tag,
- 'summary' => $this->summary,
- 'manifest' => $this->getManifest(),
- 'panels' => $this->module->panels,
- 'activePanel' => $activePanel,
- ]);
- }
-
- public function actionToolbar($tag)
- {
- $this->loadData($tag, 5);
- return $this->renderPartial('toolbar', [
- 'tag' => $tag,
- 'panels' => $this->module->panels,
- 'position' => 'bottom',
- ]);
- }
-
- public function actionDownloadMail($file)
- {
- $filePath = Yii::getAlias($this->module->panels['mail']->mailPath) . '/' . basename($file);
-
- if ((mb_strpos($file, '\\') !== false || mb_strpos($file, '/') !== false) || !is_file($filePath)) {
- throw new NotFoundHttpException('Mail file not found');
- }
-
- Yii::$app->response->sendFile($filePath);
- }
-
- private $_manifest;
-
- protected function getManifest($forceReload = false)
- {
- if ($this->_manifest === null || $forceReload) {
- if ($forceReload) {
- clearstatcache();
- }
- $indexFile = $this->module->dataPath . '/index.data';
- if (is_file($indexFile)) {
- $this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true);
- } else {
- $this->_manifest = [];
- }
- }
- return $this->_manifest;
- }
-
- public function loadData($tag, $maxRetry = 0)
- {
- // retry loading debug data because the debug data is logged in shutdown function
- // which may be delayed in some environment if xdebug is enabled.
- // See: https://github.com/yiisoft/yii2/issues/1504
- for ($retry = 0; $retry <= $maxRetry; ++$retry) {
- $manifest = $this->getManifest($retry > 0);
- if (isset($manifest[$tag])) {
- $dataFile = $this->module->dataPath . "/$tag.data";
- $data = unserialize(file_get_contents($dataFile));
- foreach ($this->module->panels as $id => $panel) {
- if (isset($data[$id])) {
- $panel->tag = $tag;
- $panel->load($data[$id]);
- } else {
- // remove the panel since it has not received any data
- unset($this->module->panels[$id]);
- }
- }
- $this->summary = $data['summary'];
- return;
- }
- sleep(1);
- }
-
- throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'.");
- }
+ /**
+ * @inheritdoc
+ */
+ public $layout = 'main';
+ /**
+ * @var \yii\debug\Module
+ */
+ public $module;
+ /**
+ * @var array the summary data (e.g. URL, time)
+ */
+ public $summary;
+
+ /**
+ * @inheritdoc
+ */
+ public function actions()
+ {
+ $actions = [];
+ foreach ($this->module->panels as $panel) {
+ $actions = array_merge($actions, $panel->actions);
+ }
+
+ return $actions;
+ }
+
+ public function actionIndex()
+ {
+ $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,
+ ]);
+ }
+
+ public function actionView($tag = null, $panel = null)
+ {
+ if ($tag === null) {
+ $tags = array_keys($this->getManifest());
+ $tag = reset($tags);
+ }
+ $this->loadData($tag);
+ if (isset($this->module->panels[$panel])) {
+ $activePanel = $this->module->panels[$panel];
+ } else {
+ $activePanel = $this->module->panels['request'];
+ }
+
+ return $this->render('view', [
+ 'tag' => $tag,
+ 'summary' => $this->summary,
+ 'manifest' => $this->getManifest(),
+ 'panels' => $this->module->panels,
+ 'activePanel' => $activePanel,
+ ]);
+ }
+
+ public function actionToolbar($tag)
+ {
+ $this->loadData($tag, 5);
+
+ return $this->renderPartial('toolbar', [
+ 'tag' => $tag,
+ 'panels' => $this->module->panels,
+ 'position' => 'bottom',
+ ]);
+ }
+
+ public function actionDownloadMail($file)
+ {
+ $filePath = Yii::getAlias($this->module->panels['mail']->mailPath) . '/' . basename($file);
+
+ if ((mb_strpos($file, '\\') !== false || mb_strpos($file, '/') !== false) || !is_file($filePath)) {
+ throw new NotFoundHttpException('Mail file not found');
+ }
+
+ Yii::$app->response->sendFile($filePath);
+ }
+
+ private $_manifest;
+
+ protected function getManifest($forceReload = false)
+ {
+ if ($this->_manifest === null || $forceReload) {
+ if ($forceReload) {
+ clearstatcache();
+ }
+ $indexFile = $this->module->dataPath . '/index.data';
+ if (is_file($indexFile)) {
+ $this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true);
+ } else {
+ $this->_manifest = [];
+ }
+ }
+
+ return $this->_manifest;
+ }
+
+ public function loadData($tag, $maxRetry = 0)
+ {
+ // retry loading debug data because the debug data is logged in shutdown function
+ // which may be delayed in some environment if xdebug is enabled.
+ // See: https://github.com/yiisoft/yii2/issues/1504
+ for ($retry = 0; $retry <= $maxRetry; ++$retry) {
+ $manifest = $this->getManifest($retry > 0);
+ if (isset($manifest[$tag])) {
+ $dataFile = $this->module->dataPath . "/$tag.data";
+ $data = unserialize(file_get_contents($dataFile));
+ foreach ($this->module->panels as $id => $panel) {
+ if (isset($data[$id])) {
+ $panel->tag = $tag;
+ $panel->load($data[$id]);
+ } else {
+ // remove the panel since it has not received any data
+ unset($this->module->panels[$id]);
+ }
+ }
+ $this->summary = $data['summary'];
+
+ return;
+ }
+ sleep(1);
+ }
+
+ throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'.");
+ }
}
diff --git a/extensions/debug/models/search/Base.php b/extensions/debug/models/search/Base.php
index f81c058a095..e8783a77ae8 100644
--- a/extensions/debug/models/search/Base.php
+++ b/extensions/debug/models/search/Base.php
@@ -19,26 +19,26 @@
*/
class Base extends Model
{
- /**
- * Adds filtering condition for a given attribute
- *
- * @param Filter $filter filter instance
- * @param string $attribute attribute to filter
- * @param boolean $partial if partial match should be used
- */
- public function addCondition(Filter $filter, $attribute, $partial = false)
- {
- $value = $this->$attribute;
+ /**
+ * Adds filtering condition for a given attribute
+ *
+ * @param Filter $filter filter instance
+ * @param string $attribute attribute to filter
+ * @param boolean $partial if partial match should be used
+ */
+ public function addCondition(Filter $filter, $attribute, $partial = false)
+ {
+ $value = $this->$attribute;
- if (mb_strpos($value, '>') !== false) {
- $value = intval(str_replace('>', '', $value));
- $filter->addMatcher($attribute, new matchers\GreaterThan(['value' => $value]));
+ if (mb_strpos($value, '>') !== false) {
+ $value = intval(str_replace('>', '', $value));
+ $filter->addMatcher($attribute, new matchers\GreaterThan(['value' => $value]));
- } elseif (mb_strpos($value, '<') !== false) {
- $value = intval(str_replace('<', '', $value));
- $filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value]));
- } else {
- $filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial]));
- }
- }
+ } elseif (mb_strpos($value, '<') !== false) {
+ $value = intval(str_replace('<', '', $value));
+ $filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value]));
+ } else {
+ $filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial]));
+ }
+ }
}
diff --git a/extensions/debug/models/search/Db.php b/extensions/debug/models/search/Db.php
index 90ee26e031b..03ae1002507 100644
--- a/extensions/debug/models/search/Db.php
+++ b/extensions/debug/models/search/Db.php
@@ -19,66 +19,66 @@
*/
class Db extends Base
{
- /**
- * @var string type of the input search value
- */
- public $type;
+ /**
+ * @var string type of the input search value
+ */
+ public $type;
- /**
- * @var integer query attribute input search value
- */
- public $query;
+ /**
+ * @var integer query attribute input search value
+ */
+ public $query;
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return [
- [['type', 'query'], 'safe'],
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['type', 'query'], 'safe'],
+ ];
+ }
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'type' => 'Type',
- 'query' => 'Query',
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'type' => 'Type',
+ 'query' => 'Query',
+ ];
+ }
- /**
- * Returns data provider with filled models. Filter applied if needed.
- *
- * @param array $params an array of parameter values indexed by parameter names
- * @param array $models data to return provider for
- * @return \yii\data\ArrayDataProvider
- */
- public function search($params, $models)
- {
- $dataProvider = new ArrayDataProvider([
- 'allModels' => $models,
- 'pagination' => false,
- 'sort' => [
- 'attributes' => ['duration', 'seq', 'type', 'query'],
- 'defaultOrder' => [
- 'duration' => SORT_DESC,
- ],
- ],
- ]);
+ /**
+ * Returns data provider with filled models. Filter applied if needed.
+ *
+ * @param array $params an array of parameter values indexed by parameter names
+ * @param array $models data to return provider for
+ * @return \yii\data\ArrayDataProvider
+ */
+ public function search($params, $models)
+ {
+ $dataProvider = new ArrayDataProvider([
+ 'allModels' => $models,
+ 'pagination' => false,
+ 'sort' => [
+ 'attributes' => ['duration', 'seq', 'type', 'query'],
+ 'defaultOrder' => [
+ 'duration' => SORT_DESC,
+ ],
+ ],
+ ]);
- if (!($this->load($params) && $this->validate())) {
- return $dataProvider;
- }
+ if (!($this->load($params) && $this->validate())) {
+ return $dataProvider;
+ }
- $filter = new Filter();
- $this->addCondition($filter, 'type', true);
- $this->addCondition($filter, 'query', true);
- $dataProvider->allModels = $filter->filter($models);
+ $filter = new Filter();
+ $this->addCondition($filter, 'type', true);
+ $this->addCondition($filter, 'query', true);
+ $dataProvider->allModels = $filter->filter($models);
- return $dataProvider;
- }
+ return $dataProvider;
+ }
}
diff --git a/extensions/debug/models/search/Debug.php b/extensions/debug/models/search/Debug.php
index c2d85e6bdba..9e6a406d647 100644
--- a/extensions/debug/models/search/Debug.php
+++ b/extensions/debug/models/search/Debug.php
@@ -19,122 +19,122 @@
*/
class Debug extends Base
{
- /**
- * @var string tag attribute input search value
- */
- public $tag;
-
- /**
- * @var string ip attribute input search value
- */
- public $ip;
-
- /**
- * @var string method attribute input search value
- */
- public $method;
-
- /**
- * @var integer ajax attribute input search value
- */
- public $ajax;
-
- /**
- * @var string url attribute input search value
- */
- public $url;
-
- /**
- * @var string status code attribute input search value
- */
- public $statusCode;
-
- /**
- * @var integer sql count attribute input search value
- */
- public $sqlCount;
-
- /**
- * @var integer total mail count attribute input search value
- */
- public $mailCount;
-
- /**
- * @var array critical codes, used to determine grid row options.
- */
- public $criticalCodes = [400, 404, 500];
-
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return [
- [['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount', 'mailCount'], 'safe'],
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'tag' => 'Tag',
- 'ip' => 'Ip',
- 'method' => 'Method',
- 'ajax' => 'Ajax',
- 'url' => 'url',
- 'statusCode' => 'Status code',
- 'sqlCount' => 'Query Count',
- 'mailCount' => 'Mail Count',
- ];
- }
-
- /**
- * Returns data provider with filled models. Filter applied if needed.
- * @param array $params an array of parameter values indexed by parameter names
- * @param array $models data to return provider for
- * @return \yii\data\ArrayDataProvider
- */
- public function search($params, $models)
- {
- $dataProvider = new ArrayDataProvider([
- 'allModels' => $models,
- 'sort' => [
- 'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount', 'mailCount'],
- ],
- 'pagination' => [
- 'pageSize' => 50,
- ],
- ]);
-
- if (!($this->load($params) && $this->validate())) {
- return $dataProvider;
- }
-
- $filter = new Filter();
- $this->addCondition($filter, 'tag', true);
- $this->addCondition($filter, 'ip', true);
- $this->addCondition($filter, 'method');
- $this->addCondition($filter, 'ajax');
- $this->addCondition($filter, 'url', true);
- $this->addCondition($filter, 'statusCode');
- $this->addCondition($filter, 'sqlCount');
- $this->addCondition($filter, 'mailCount');
- $dataProvider->allModels = $filter->filter($models);
-
- return $dataProvider;
- }
-
- /**
- * Checks if code is critical.
- *
- * @param integer $code
- * @return boolean
- */
- public function isCodeCritical($code)
- {
- return in_array($code, $this->criticalCodes);
- }
+ /**
+ * @var string tag attribute input search value
+ */
+ public $tag;
+
+ /**
+ * @var string ip attribute input search value
+ */
+ public $ip;
+
+ /**
+ * @var string method attribute input search value
+ */
+ public $method;
+
+ /**
+ * @var integer ajax attribute input search value
+ */
+ public $ajax;
+
+ /**
+ * @var string url attribute input search value
+ */
+ public $url;
+
+ /**
+ * @var string status code attribute input search value
+ */
+ public $statusCode;
+
+ /**
+ * @var integer sql count attribute input search value
+ */
+ public $sqlCount;
+
+ /**
+ * @var integer total mail count attribute input search value
+ */
+ public $mailCount;
+
+ /**
+ * @var array critical codes, used to determine grid row options.
+ */
+ public $criticalCodes = [400, 404, 500];
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount', 'mailCount'], 'safe'],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tag' => 'Tag',
+ 'ip' => 'Ip',
+ 'method' => 'Method',
+ 'ajax' => 'Ajax',
+ 'url' => 'url',
+ 'statusCode' => 'Status code',
+ 'sqlCount' => 'Query Count',
+ 'mailCount' => 'Mail Count',
+ ];
+ }
+
+ /**
+ * Returns data provider with filled models. Filter applied if needed.
+ * @param array $params an array of parameter values indexed by parameter names
+ * @param array $models data to return provider for
+ * @return \yii\data\ArrayDataProvider
+ */
+ public function search($params, $models)
+ {
+ $dataProvider = new ArrayDataProvider([
+ 'allModels' => $models,
+ 'sort' => [
+ 'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount', 'mailCount'],
+ ],
+ 'pagination' => [
+ 'pageSize' => 50,
+ ],
+ ]);
+
+ if (!($this->load($params) && $this->validate())) {
+ return $dataProvider;
+ }
+
+ $filter = new Filter();
+ $this->addCondition($filter, 'tag', true);
+ $this->addCondition($filter, 'ip', true);
+ $this->addCondition($filter, 'method');
+ $this->addCondition($filter, 'ajax');
+ $this->addCondition($filter, 'url', true);
+ $this->addCondition($filter, 'statusCode');
+ $this->addCondition($filter, 'sqlCount');
+ $this->addCondition($filter, 'mailCount');
+ $dataProvider->allModels = $filter->filter($models);
+
+ return $dataProvider;
+ }
+
+ /**
+ * Checks if code is critical.
+ *
+ * @param integer $code
+ * @return boolean
+ */
+ public function isCodeCritical($code)
+ {
+ return in_array($code, $this->criticalCodes);
+ }
}
diff --git a/extensions/debug/models/search/Log.php b/extensions/debug/models/search/Log.php
index ba19c5aaae6..56b3057a52b 100644
--- a/extensions/debug/models/search/Log.php
+++ b/extensions/debug/models/search/Log.php
@@ -19,70 +19,70 @@
*/
class Log extends Base
{
- /**
- * @var string ip attribute input search value
- */
- public $level;
+ /**
+ * @var string ip attribute input search value
+ */
+ public $level;
- /**
- * @var string method attribute input search value
- */
- public $category;
+ /**
+ * @var string method attribute input search value
+ */
+ public $category;
- /**
- * @var integer message attribute input search value
- */
- public $message;
+ /**
+ * @var integer message attribute input search value
+ */
+ public $message;
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return [
- [['level', 'message', 'category'], 'safe'],
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['level', 'message', 'category'], 'safe'],
+ ];
+ }
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'level' => 'Level',
- 'category' => 'Category',
- 'message' => 'Message',
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'level' => 'Level',
+ 'category' => 'Category',
+ 'message' => 'Message',
+ ];
+ }
- /**
- * Returns data provider with filled models. Filter applied if needed.
- *
- * @param array $params an array of parameter values indexed by parameter names
- * @param array $models data to return provider for
- * @return \yii\data\ArrayDataProvider
- */
- public function search($params, $models)
- {
- $dataProvider = new ArrayDataProvider([
- 'allModels' => $models,
- 'pagination' => false,
- 'sort' => [
- 'attributes' => ['time', 'level', 'category', 'message'],
- ],
- ]);
+ /**
+ * Returns data provider with filled models. Filter applied if needed.
+ *
+ * @param array $params an array of parameter values indexed by parameter names
+ * @param array $models data to return provider for
+ * @return \yii\data\ArrayDataProvider
+ */
+ public function search($params, $models)
+ {
+ $dataProvider = new ArrayDataProvider([
+ 'allModels' => $models,
+ 'pagination' => false,
+ 'sort' => [
+ 'attributes' => ['time', 'level', 'category', 'message'],
+ ],
+ ]);
- if (!($this->load($params) && $this->validate())) {
- return $dataProvider;
- }
+ if (!($this->load($params) && $this->validate())) {
+ return $dataProvider;
+ }
- $filter = new Filter();
- $this->addCondition($filter, 'level');
- $this->addCondition($filter, 'category', true);
- $this->addCondition($filter, 'message', true);
- $dataProvider->allModels = $filter->filter($models);
+ $filter = new Filter();
+ $this->addCondition($filter, 'level');
+ $this->addCondition($filter, 'category', true);
+ $this->addCondition($filter, 'message', true);
+ $dataProvider->allModels = $filter->filter($models);
- return $dataProvider;
- }
+ return $dataProvider;
+ }
}
diff --git a/extensions/debug/models/search/Mail.php b/extensions/debug/models/search/Mail.php
index 3cd7a279cfa..5afeb98a282 100644
--- a/extensions/debug/models/search/Mail.php
+++ b/extensions/debug/models/search/Mail.php
@@ -13,112 +13,112 @@
*/
class Mail extends Base
{
- /**
- * @var string from attribute input search value
- */
- public $from;
-
- /**
- * @var string to attribute input search value
- */
- public $to;
-
- /**
- * @var string reply attribute input search value
- */
- public $reply;
-
- /**
- * @var string cc attribute input search value
- */
- public $cc;
-
- /**
- * @var string bcc attribute input search value
- */
- public $bcc;
-
- /**
- * @var string subject attribute input search value
- */
- public $subject;
-
- /**
- * @var string body attribute input search value
- */
- public $body;
-
- /**
- * @var string charset attribute input search value
- */
- public $charset;
-
- /**
- * @var string headers attribute input search value
- */
- public $headers;
-
- /**
- * @var string file attribute input search value
- */
- public $file;
-
- public function rules()
- {
- return [
- [['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], 'safe'],
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'from' => 'From',
- 'to' => 'To',
- 'reply' => 'Reply',
- 'cc' => 'Copy receiver',
- 'bcc' => 'Hidden copy receiver',
- 'subject' => 'Subject',
- 'charset' => 'Charset'
- ];
- }
-
- /**
- * Returns data provider with filled models. Filter applied if needed.
- * @param array $params
- * @param array $models
- * @return \yii\data\ArrayDataProvider
- */
- public function search($params, $models)
- {
- $dataProvider = new ArrayDataProvider([
- 'allModels' => $models,
- 'pagination' => [
- 'pageSize' => 20,
- ],
- 'sort' => [
- 'attributes' => ['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'],
- ],
- ]);
-
- if (!($this->load($params) && $this->validate())) {
- return $dataProvider;
- }
-
- $filter = new Filter();
- $this->addCondition($filter, 'from', true);
- $this->addCondition($filter, 'to', true);
- $this->addCondition($filter, 'reply', true);
- $this->addCondition($filter, 'cc', true);
- $this->addCondition($filter, 'bcc', true);
- $this->addCondition($filter, 'subject', true);
- $this->addCondition($filter, 'body', true);
- $this->addCondition($filter, 'charset', true);
- $dataProvider->allModels = $filter->filter($models);
-
- return $dataProvider;
- }
+ /**
+ * @var string from attribute input search value
+ */
+ public $from;
+
+ /**
+ * @var string to attribute input search value
+ */
+ public $to;
+
+ /**
+ * @var string reply attribute input search value
+ */
+ public $reply;
+
+ /**
+ * @var string cc attribute input search value
+ */
+ public $cc;
+
+ /**
+ * @var string bcc attribute input search value
+ */
+ public $bcc;
+
+ /**
+ * @var string subject attribute input search value
+ */
+ public $subject;
+
+ /**
+ * @var string body attribute input search value
+ */
+ public $body;
+
+ /**
+ * @var string charset attribute input search value
+ */
+ public $charset;
+
+ /**
+ * @var string headers attribute input search value
+ */
+ public $headers;
+
+ /**
+ * @var string file attribute input search value
+ */
+ public $file;
+
+ public function rules()
+ {
+ return [
+ [['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], 'safe'],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'from' => 'From',
+ 'to' => 'To',
+ 'reply' => 'Reply',
+ 'cc' => 'Copy receiver',
+ 'bcc' => 'Hidden copy receiver',
+ 'subject' => 'Subject',
+ 'charset' => 'Charset'
+ ];
+ }
+
+ /**
+ * Returns data provider with filled models. Filter applied if needed.
+ * @param array $params
+ * @param array $models
+ * @return \yii\data\ArrayDataProvider
+ */
+ public function search($params, $models)
+ {
+ $dataProvider = new ArrayDataProvider([
+ 'allModels' => $models,
+ 'pagination' => [
+ 'pageSize' => 20,
+ ],
+ 'sort' => [
+ 'attributes' => ['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'],
+ ],
+ ]);
+
+ if (!($this->load($params) && $this->validate())) {
+ return $dataProvider;
+ }
+
+ $filter = new Filter();
+ $this->addCondition($filter, 'from', true);
+ $this->addCondition($filter, 'to', true);
+ $this->addCondition($filter, 'reply', true);
+ $this->addCondition($filter, 'cc', true);
+ $this->addCondition($filter, 'bcc', true);
+ $this->addCondition($filter, 'subject', true);
+ $this->addCondition($filter, 'body', true);
+ $this->addCondition($filter, 'charset', true);
+ $dataProvider->allModels = $filter->filter($models);
+
+ return $dataProvider;
+ }
}
diff --git a/extensions/debug/models/search/Profile.php b/extensions/debug/models/search/Profile.php
index f39f4ca98ec..79c31a63ddb 100644
--- a/extensions/debug/models/search/Profile.php
+++ b/extensions/debug/models/search/Profile.php
@@ -19,66 +19,66 @@
*/
class Profile extends Base
{
- /**
- * @var string method attribute input search value
- */
- public $category;
+ /**
+ * @var string method attribute input search value
+ */
+ public $category;
- /**
- * @var integer info attribute input search value
- */
- public $info;
+ /**
+ * @var integer info attribute input search value
+ */
+ public $info;
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return [
- [['category', 'info'], 'safe'],
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['category', 'info'], 'safe'],
+ ];
+ }
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'category' => 'Category',
- 'info' => 'Info',
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'category' => 'Category',
+ 'info' => 'Info',
+ ];
+ }
- /**
- * Returns data provider with filled models. Filter applied if needed.
- *
- * @param array $params an array of parameter values indexed by parameter names
- * @param array $models data to return provider for
- * @return \yii\data\ArrayDataProvider
- */
- public function search($params, $models)
- {
- $dataProvider = new ArrayDataProvider([
- 'allModels' => $models,
- 'pagination' => false,
- 'sort' => [
- 'attributes' => ['category', 'seq', 'duration', 'info'],
- 'defaultOrder' => [
- 'seq' => SORT_ASC,
- ],
- ],
- ]);
+ /**
+ * Returns data provider with filled models. Filter applied if needed.
+ *
+ * @param array $params an array of parameter values indexed by parameter names
+ * @param array $models data to return provider for
+ * @return \yii\data\ArrayDataProvider
+ */
+ public function search($params, $models)
+ {
+ $dataProvider = new ArrayDataProvider([
+ 'allModels' => $models,
+ 'pagination' => false,
+ 'sort' => [
+ 'attributes' => ['category', 'seq', 'duration', 'info'],
+ 'defaultOrder' => [
+ 'seq' => SORT_ASC,
+ ],
+ ],
+ ]);
- if (!($this->load($params) && $this->validate())) {
- return $dataProvider;
- }
+ if (!($this->load($params) && $this->validate())) {
+ return $dataProvider;
+ }
- $filter = new Filter();
- $this->addCondition($filter, 'category', true);
- $this->addCondition($filter, 'info', true);
- $dataProvider->allModels = $filter->filter($models);
+ $filter = new Filter();
+ $this->addCondition($filter, 'category', true);
+ $this->addCondition($filter, 'info', true);
+ $dataProvider->allModels = $filter->filter($models);
- return $dataProvider;
- }
+ return $dataProvider;
+ }
}
diff --git a/extensions/debug/panels/ConfigPanel.php b/extensions/debug/panels/ConfigPanel.php
index 4ac76b44081..048aef89865 100644
--- a/extensions/debug/panels/ConfigPanel.php
+++ b/extensions/debug/panels/ConfigPanel.php
@@ -20,82 +20,83 @@
*/
class ConfigPanel extends Panel
{
- /**
- * @inheritdoc
- */
- public function getName()
- {
- return 'Configuration';
- }
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return 'Configuration';
+ }
- /**
- * @inheritdoc
- */
- public function getSummary()
- {
- return Yii::$app->view->render('panels/config/summary', ['panel' => $this]);
- }
+ /**
+ * @inheritdoc
+ */
+ public function getSummary()
+ {
+ return Yii::$app->view->render('panels/config/summary', ['panel' => $this]);
+ }
- /**
- * @inheritdoc
- */
- public function getDetail()
- {
- return Yii::$app->view->render('panels/config/detail', ['panel' => $this]);
- }
+ /**
+ * @inheritdoc
+ */
+ public function getDetail()
+ {
+ return Yii::$app->view->render('panels/config/detail', ['panel' => $this]);
+ }
- /**
- * Returns data about extensions
- *
- * @return array
- */
- public function getExtensions()
- {
- $data = [];
- foreach ($this->data['extensions'] as $extension) {
- $data[$extension['name']] = $extension['version'];
- }
- return $data;
- }
+ /**
+ * Returns data about extensions
+ *
+ * @return array
+ */
+ public function getExtensions()
+ {
+ $data = [];
+ foreach ($this->data['extensions'] as $extension) {
+ $data[$extension['name']] = $extension['version'];
+ }
- /**
- * 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('%^.*(.*).*$%ms', '$1', $pinfo);
- $phpinfo = str_replace('
HTML;
- }
-
- private $_timings;
-
- public function calculateTimings()
- {
- if ($this->_timings !== null) {
- return $this->_timings;
- }
- $messages = $this->data['messages'];
- $timings = [];
- $stack = [];
- foreach ($messages as $i => $log) {
- list($token, $level, $category, $timestamp) = $log;
- $log[5] = $i;
- if ($level == Logger::LEVEL_PROFILE_BEGIN) {
- $stack[] = $log;
- } elseif ($level == Logger::LEVEL_PROFILE_END) {
- if (($last = array_pop($stack)) !== null && $last[0] === $token) {
- $timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]];
- }
- }
- }
-
- $now = microtime(true);
- while (($last = array_pop($stack)) !== null) {
- $delta = $now - $last[3];
- $timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]];
- }
- ksort($timings);
- return $this->_timings = $timings;
- }
-
- public function save()
- {
- $target = $this->module->logTarget;
- $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\elasticsearch\Connection::httpRequest']);
- return ['messages' => $messages];
- }
+ }
+
+ private $_timings;
+
+ public function calculateTimings()
+ {
+ if ($this->_timings !== null) {
+ return $this->_timings;
+ }
+ $messages = $this->data['messages'];
+ $timings = [];
+ $stack = [];
+ foreach ($messages as $i => $log) {
+ list($token, $level, $category, $timestamp) = $log;
+ $log[5] = $i;
+ if ($level == Logger::LEVEL_PROFILE_BEGIN) {
+ $stack[] = $log;
+ } elseif ($level == Logger::LEVEL_PROFILE_END) {
+ if (($last = array_pop($stack)) !== null && $last[0] === $token) {
+ $timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]];
+ }
+ }
+ }
+
+ $now = microtime(true);
+ while (($last = array_pop($stack)) !== null) {
+ $delta = $now - $last[3];
+ $timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]];
+ }
+ ksort($timings);
+
+ return $this->_timings = $timings;
+ }
+
+ public function save()
+ {
+ $target = $this->module->logTarget;
+ $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\elasticsearch\Connection::httpRequest']);
+
+ return ['messages' => $messages];
+ }
}
diff --git a/extensions/elasticsearch/Exception.php b/extensions/elasticsearch/Exception.php
index 2510072abcb..dd4c1628e69 100644
--- a/extensions/elasticsearch/Exception.php
+++ b/extensions/elasticsearch/Exception.php
@@ -15,11 +15,11 @@
*/
class Exception extends \yii\db\Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Elasticsearch Database Exception';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Elasticsearch Database Exception';
+ }
}
diff --git a/extensions/elasticsearch/Query.php b/extensions/elasticsearch/Query.php
index f392d161f68..63a2c887846 100644
--- a/extensions/elasticsearch/Query.php
+++ b/extensions/elasticsearch/Query.php
@@ -54,451 +54,463 @@
*/
class Query extends Component implements QueryInterface
{
- use QueryTrait;
-
- /**
- * @var array the fields being retrieved from the documents. For example, `['id', 'name']`.
- * If not set, it means retrieving all fields. An empty array will result in no fields being
- * retrieved. This means that only the primaryKey of a record will be available in the result.
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html#search-request-fields
- * @see fields()
- */
- public $fields;
- /**
- * @var string|array The index to retrieve data from. This can be a string representing a single index
- * or a an array of multiple indexes. If this is not set, indexes are being queried.
- * @see from()
- */
- public $index;
- /**
- * @var string|array The type to retrieve data from. This can be a string representing a single type
- * or a an array of multiple types. If this is not set, all types are being queried.
- * @see from()
- */
- public $type;
- /**
- * @var integer A search timeout, bounding the search request to be executed within the specified time value
- * and bail with the hits accumulated up to that point when expired. Defaults to no timeout.
- * @see timeout()
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3
- */
- public $timeout;
- /**
- * @var array|string The query part of this search query. This is an array or json string that follows the format of
- * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html).
- */
- public $query;
- /**
- * @var array|string The filter part of this search query. This is an array or json string that follows the format of
- * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html).
- */
- public $filter;
-
- public $facets = [];
-
- public function init()
- {
- parent::init();
- // setting the default limit according to elasticsearch defaults
- // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3
- if ($this->limit === null) {
- $this->limit = 10;
- }
- }
-
- /**
- * Creates a DB command that can be used to execute this query.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return Command the created DB command instance.
- */
- public function createCommand($db = null)
- {
- if ($db === null) {
- $db = Yii::$app->getComponent('elasticsearch');
- }
-
- $commandConfig = $db->getQueryBuilder()->build($this);
- return $db->createCommand($commandConfig);
- }
-
- /**
- * Executes the query and returns all results as an array.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- $result = $this->createCommand($db)->search();
- if (empty($result['hits']['hits'])) {
- return [];
- }
- $rows = $result['hits']['hits'];
- if ($this->indexBy === null && $this->fields === null) {
- return $rows;
- }
- $models = [];
- foreach ($rows as $key => $row) {
- if ($this->fields !== null) {
- $row['_source'] = isset($row['fields']) ? $row['fields'] : [];
- unset($row['fields']);
- }
- if ($this->indexBy !== null) {
- if (is_string($this->indexBy)) {
- $key = $row['_source'][$this->indexBy];
- } else {
- $key = call_user_func($this->indexBy, $row);
- }
- }
- $models[$key] = $row;
- }
- return $models;
- }
-
- /**
- * Executes the query and returns a single row of result.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
- * results in nothing.
- */
- public function one($db = null)
- {
- $result = $this->createCommand($db)->search(['size' => 1]);
- if (empty($result['hits']['hits'])) {
- return false;
- }
- $record = reset($result['hits']['hits']);
- if ($this->fields !== null) {
- $record['_source'] = isset($record['fields']) ? $record['fields'] : [];
- unset($record['fields']);
- }
- return $record;
- }
-
- /**
- * Executes the query and returns the complete search result including e.g. hits, facets, totalCount.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @param array $options The options given with this query. Possible options are:
- * - [routing](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#search-routing)
- * - [search_type](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html)
- * @return array the query results.
- */
- public function search($db = null, $options = [])
- {
- $result = $this->createCommand($db)->search($options);
- if (!empty($result['hits']['hits']) && ($this->indexBy === null || $this->fields === null)) {
- $rows = [];
- foreach ($result['hits']['hits'] as $key => $row) {
- if ($this->fields !== null) {
- $row['_source'] = isset($row['fields']) ? $row['fields'] : [];
- unset($row['fields']);
- }
- if ($this->indexBy !== null) {
- if (is_string($this->indexBy)) {
- $key = $row['_source'][$this->indexBy];
- } else {
- $key = call_user_func($this->indexBy, $row);
- }
- }
- $rows[$key] = $row;
- }
- $result['hits']['hits'] = $rows;
- }
- return $result;
- }
-
- // TODO add query stats http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#stats-groups
-
- // TODO add scroll/scan http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html#scan
-
- /**
- * Executes the query and deletes all matching documents.
- *
- * This will not run facet queries.
- *
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function delete($db = null)
- {
- // TODO implement http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
- throw new NotSupportedException('Delete by query is not implemented yet.');
- }
-
- /**
- * Returns the query result as a scalar value.
- * The value returned will be the specified field in the first document of the query results.
- * @param string $field name of the attribute to select
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return string the value of the specified attribute in the first record of the query result.
- * Null is returned if the query result is empty or the field does not exist.
- */
- public function scalar($field, $db = null)
- {
- $record = self::one($db); // TODO limit fields to the one required
- if ($record !== false && isset($record['_source'][$field])) {
- return $record['_source'][$field];
- } else {
- return null;
- }
- }
-
- /**
- * Executes the query and returns the first column of the result.
- * @param string $field the field to query over
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return array the first column of the query result. An empty array is returned if the query results in nothing.
- */
- public function column($field, $db = null)
- {
- $command = $this->createCommand($db);
- $command->queryParts['fields'] = [$field];
- $result = $command->search();
- if (empty($result['hits']['hits'])) {
- return [];
- }
- $column = [];
- foreach ($result['hits']['hits'] as $row) {
- $column[] = isset($row['fields'][$field]) ? $row['fields'][$field] : null;
- }
- return $column;
- }
-
- /**
- * Returns the number of records.
- * @param string $q the COUNT expression. This parameter is ignored by this implementation.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return integer number of records
- */
- public function count($q = '*', $db = null)
- {
- // TODO consider sending to _count api instead of _search for performance
- // only when no facety are registerted.
- // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-count.html
-
- $options = [];
- $options['search_type'] = 'count';
- return $this->createCommand($db)->search($options)['hits']['total'];
- }
-
- /**
- * Returns a value indicating whether the query result contains any row of data.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `elasticsearch` application component will be used.
- * @return boolean whether the query result contains any row of data.
- */
- public function exists($db = null)
- {
- return self::one($db) !== false;
- }
-
- /**
- * Adds a facet search to this query.
- * @param string $name the name of this facet
- * @param string $type the facet type. e.g. `terms`, `range`, `histogram`...
- * @param string|array $options the configuration options for this facet. Can be an array or a json string.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-query-facet.html
- */
- public function addFacet($name, $type, $options)
- {
- $this->facets[$name] = [$type => $options];
- return $this;
- }
-
- /**
- * The `terms facet` allow to specify field facets that return the N most frequent terms.
- * @param string $name the name of this facet
- * @param array $options additional option. Please refer to the elasticsearch documentation for details.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-facet.html
- */
- public function addTermFacet($name, $options)
- {
- return $this->addFacet($name, 'terms', $options);
- }
-
- /**
- * Range facet allows to specify a set of ranges and get both the number of docs (count) that fall
- * within each range, and aggregated data either based on the field, or using another field.
- * @param string $name the name of this facet
- * @param array $options additional option. Please refer to the elasticsearch documentation for details.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-range-facet.html
- */
- public function addRangeFacet($name, $options)
- {
- return $this->addFacet($name, 'range', $options);
- }
-
- /**
- * The histogram facet works with numeric data by building a histogram across intervals of the field values.
- * Each value is "rounded" into an interval (or placed in a bucket), and statistics are provided per
- * interval/bucket (count and total).
- * @param string $name the name of this facet
- * @param array $options additional option. Please refer to the elasticsearch documentation for details.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-histogram-facet.html
- */
- public function addHistogramFacet($name, $options)
- {
- return $this->addFacet($name, 'histogram', $options);
- }
-
- /**
- * A specific histogram facet that can work with date field types enhancing it over the regular histogram facet.
- * @param string $name the name of this facet
- * @param array $options additional option. Please refer to the elasticsearch documentation for details.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-date-histogram-facet.html
- */
- public function addDateHistogramFacet($name, $options)
- {
- return $this->addFacet($name, 'date_histogram', $options);
- }
-
- /**
- * A filter facet (not to be confused with a facet filter) allows you to return a count of the hits matching the filter.
- * The filter itself can be expressed using the Query DSL.
- * @param string $name the name of this facet
- * @param string $filter the query in Query DSL
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-filter-facet.html
- */
- public function addFilterFacet($name, $filter)
- {
- return $this->addFacet($name, 'filter', $filter);
- }
-
- /**
- * A facet query allows to return a count of the hits matching the facet query.
- * The query itself can be expressed using the Query DSL.
- * @param string $name the name of this facet
- * @param string $query the query in Query DSL
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-query-facet.html
- */
- public function addQueryFacet($name, $query)
- {
- return $this->addFacet($name, 'query', $query);
- }
-
- /**
- * Statistical facet allows to compute statistical data on a numeric fields. The statistical data include count,
- * total, sum of squares, mean (average), minimum, maximum, variance, and standard deviation.
- * @param string $name the name of this facet
- * @param array $options additional option. Please refer to the elasticsearch documentation for details.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-statistical-facet.html
- */
- public function addStatisticalFacet($name, $options)
- {
- return $this->addFacet($name, 'statistical', $options);
- }
-
- /**
- * The `terms_stats` facet combines both the terms and statistical allowing to compute stats computed on a field,
- * per term value driven by another field.
- * @param string $name the name of this facet
- * @param array $options additional option. Please refer to the elasticsearch documentation for details.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-stats-facet.html
- */
- public function addTermsStatsFacet($name, $options)
- {
- return $this->addFacet($name, 'terms_stats', $options);
- }
-
- /**
- * The `geo_distance` facet is a facet providing information for ranges of distances from a provided `geo_point`
- * including count of the number of hits that fall within each range, and aggregation information (like `total`).
- * @param string $name the name of this facet
- * @param array $options additional option. Please refer to the elasticsearch documentation for details.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-geo-distance-facet.html
- */
- public function addGeoDistanceFacet($name, $options)
- {
- return $this->addFacet($name, 'geo_distance', $options);
- }
-
- // TODO add suggesters http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html
-
- // TODO add validate query http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-validate.html
-
- // TODO support multi query via static method http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html
-
- /**
- * Sets the querypart of this search query.
- * @param string $query
- * @return static the query object itself
- */
- public function query($query)
- {
- $this->query = $query;
- return $this;
- }
-
- /**
- * Sets the filter part of this search query.
- * @param string $filter
- * @return static the query object itself
- */
- public function filter($filter)
- {
- $this->filter = $filter;
- return $this;
- }
-
- /**
- * Sets the index and type to retrieve documents from.
- * @param string|array $index The index to retrieve data from. This can be a string representing a single index
- * or a an array of multiple indexes. If this is `null` it means that all indexes are being queried.
- * @param string|array $type The type to retrieve data from. This can be a string representing a single type
- * or a an array of multiple types. If this is `null` it means that all types are being queried.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-search.html#search-multi-index-type
- */
- public function from($index, $type = null)
- {
- $this->index = $index;
- $this->type = $type;
- return $this;
- }
-
- /**
- * Sets the fields to retrieve from the documents.
- * @param array $fields the fields to be selected.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html
- */
- public function fields($fields)
- {
- if (is_array($fields) || $fields === null) {
- $this->fields = $fields;
- } else {
- $this->fields = func_get_args();
- }
- return $this;
- }
-
- /**
- * Sets the search timeout.
- * @param integer $timeout A search timeout, bounding the search request to be executed within the specified time value
- * and bail with the hits accumulated up to that point when expired. Defaults to no timeout.
- * @return static the query object itself
- * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3
- */
- public function timeout($timeout)
- {
- $this->timeout = $timeout;
- return $this;
- }
+ use QueryTrait;
+
+ /**
+ * @var array the fields being retrieved from the documents. For example, `['id', 'name']`.
+ * If not set, it means retrieving all fields. An empty array will result in no fields being
+ * retrieved. This means that only the primaryKey of a record will be available in the result.
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html#search-request-fields
+ * @see fields()
+ */
+ public $fields;
+ /**
+ * @var string|array The index to retrieve data from. This can be a string representing a single index
+ * or a an array of multiple indexes. If this is not set, indexes are being queried.
+ * @see from()
+ */
+ public $index;
+ /**
+ * @var string|array The type to retrieve data from. This can be a string representing a single type
+ * or a an array of multiple types. If this is not set, all types are being queried.
+ * @see from()
+ */
+ public $type;
+ /**
+ * @var integer A search timeout, bounding the search request to be executed within the specified time value
+ * and bail with the hits accumulated up to that point when expired. Defaults to no timeout.
+ * @see timeout()
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3
+ */
+ public $timeout;
+ /**
+ * @var array|string The query part of this search query. This is an array or json string that follows the format of
+ * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html).
+ */
+ public $query;
+ /**
+ * @var array|string The filter part of this search query. This is an array or json string that follows the format of
+ * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html).
+ */
+ public $filter;
+
+ public $facets = [];
+
+ public function init()
+ {
+ parent::init();
+ // setting the default limit according to elasticsearch defaults
+ // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3
+ if ($this->limit === null) {
+ $this->limit = 10;
+ }
+ }
+
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return Command the created DB command instance.
+ */
+ public function createCommand($db = null)
+ {
+ if ($db === null) {
+ $db = Yii::$app->getComponent('elasticsearch');
+ }
+
+ $commandConfig = $db->getQueryBuilder()->build($this);
+
+ return $db->createCommand($commandConfig);
+ }
+
+ /**
+ * Executes the query and returns all results as an array.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $result = $this->createCommand($db)->search();
+ if (empty($result['hits']['hits'])) {
+ return [];
+ }
+ $rows = $result['hits']['hits'];
+ if ($this->indexBy === null && $this->fields === null) {
+ return $rows;
+ }
+ $models = [];
+ foreach ($rows as $key => $row) {
+ if ($this->fields !== null) {
+ $row['_source'] = isset($row['fields']) ? $row['fields'] : [];
+ unset($row['fields']);
+ }
+ if ($this->indexBy !== null) {
+ if (is_string($this->indexBy)) {
+ $key = $row['_source'][$this->indexBy];
+ } else {
+ $key = call_user_func($this->indexBy, $row);
+ }
+ }
+ $models[$key] = $row;
+ }
+
+ return $models;
+ }
+
+ /**
+ * Executes the query and returns a single row of result.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ */
+ public function one($db = null)
+ {
+ $result = $this->createCommand($db)->search(['size' => 1]);
+ if (empty($result['hits']['hits'])) {
+ return false;
+ }
+ $record = reset($result['hits']['hits']);
+ if ($this->fields !== null) {
+ $record['_source'] = isset($record['fields']) ? $record['fields'] : [];
+ unset($record['fields']);
+ }
+
+ return $record;
+ }
+
+ /**
+ * Executes the query and returns the complete search result including e.g. hits, facets, totalCount.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @param array $options The options given with this query. Possible options are:
+ * - [routing](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#search-routing)
+ * - [search_type](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html)
+ * @return array the query results.
+ */
+ public function search($db = null, $options = [])
+ {
+ $result = $this->createCommand($db)->search($options);
+ if (!empty($result['hits']['hits']) && ($this->indexBy === null || $this->fields === null)) {
+ $rows = [];
+ foreach ($result['hits']['hits'] as $key => $row) {
+ if ($this->fields !== null) {
+ $row['_source'] = isset($row['fields']) ? $row['fields'] : [];
+ unset($row['fields']);
+ }
+ if ($this->indexBy !== null) {
+ if (is_string($this->indexBy)) {
+ $key = $row['_source'][$this->indexBy];
+ } else {
+ $key = call_user_func($this->indexBy, $row);
+ }
+ }
+ $rows[$key] = $row;
+ }
+ $result['hits']['hits'] = $rows;
+ }
+
+ return $result;
+ }
+
+ // TODO add query stats http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#stats-groups
+
+ // TODO add scroll/scan http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html#scan
+
+ /**
+ * Executes the query and deletes all matching documents.
+ *
+ * This will not run facet queries.
+ *
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function delete($db = null)
+ {
+ // TODO implement http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
+ throw new NotSupportedException('Delete by query is not implemented yet.');
+ }
+
+ /**
+ * Returns the query result as a scalar value.
+ * The value returned will be the specified field in the first document of the query results.
+ * @param string $field name of the attribute to select
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return string the value of the specified attribute in the first record of the query result.
+ * Null is returned if the query result is empty or the field does not exist.
+ */
+ public function scalar($field, $db = null)
+ {
+ $record = self::one($db); // TODO limit fields to the one required
+ if ($record !== false && isset($record['_source'][$field])) {
+ return $record['_source'][$field];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Executes the query and returns the first column of the result.
+ * @param string $field the field to query over
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return array the first column of the query result. An empty array is returned if the query results in nothing.
+ */
+ public function column($field, $db = null)
+ {
+ $command = $this->createCommand($db);
+ $command->queryParts['fields'] = [$field];
+ $result = $command->search();
+ if (empty($result['hits']['hits'])) {
+ return [];
+ }
+ $column = [];
+ foreach ($result['hits']['hits'] as $row) {
+ $column[] = isset($row['fields'][$field]) ? $row['fields'][$field] : null;
+ }
+
+ return $column;
+ }
+
+ /**
+ * Returns the number of records.
+ * @param string $q the COUNT expression. This parameter is ignored by this implementation.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return integer number of records
+ */
+ public function count($q = '*', $db = null)
+ {
+ // TODO consider sending to _count api instead of _search for performance
+ // only when no facety are registerted.
+ // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-count.html
+
+ $options = [];
+ $options['search_type'] = 'count';
+
+ return $this->createCommand($db)->search($options)['hits']['total'];
+ }
+
+ /**
+ * Returns a value indicating whether the query result contains any row of data.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `elasticsearch` application component will be used.
+ * @return boolean whether the query result contains any row of data.
+ */
+ public function exists($db = null)
+ {
+ return self::one($db) !== false;
+ }
+
+ /**
+ * Adds a facet search to this query.
+ * @param string $name the name of this facet
+ * @param string $type the facet type. e.g. `terms`, `range`, `histogram`...
+ * @param string|array $options the configuration options for this facet. Can be an array or a json string.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-query-facet.html
+ */
+ public function addFacet($name, $type, $options)
+ {
+ $this->facets[$name] = [$type => $options];
+
+ return $this;
+ }
+
+ /**
+ * The `terms facet` allow to specify field facets that return the N most frequent terms.
+ * @param string $name the name of this facet
+ * @param array $options additional option. Please refer to the elasticsearch documentation for details.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-facet.html
+ */
+ public function addTermFacet($name, $options)
+ {
+ return $this->addFacet($name, 'terms', $options);
+ }
+
+ /**
+ * Range facet allows to specify a set of ranges and get both the number of docs (count) that fall
+ * within each range, and aggregated data either based on the field, or using another field.
+ * @param string $name the name of this facet
+ * @param array $options additional option. Please refer to the elasticsearch documentation for details.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-range-facet.html
+ */
+ public function addRangeFacet($name, $options)
+ {
+ return $this->addFacet($name, 'range', $options);
+ }
+
+ /**
+ * The histogram facet works with numeric data by building a histogram across intervals of the field values.
+ * Each value is "rounded" into an interval (or placed in a bucket), and statistics are provided per
+ * interval/bucket (count and total).
+ * @param string $name the name of this facet
+ * @param array $options additional option. Please refer to the elasticsearch documentation for details.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-histogram-facet.html
+ */
+ public function addHistogramFacet($name, $options)
+ {
+ return $this->addFacet($name, 'histogram', $options);
+ }
+
+ /**
+ * A specific histogram facet that can work with date field types enhancing it over the regular histogram facet.
+ * @param string $name the name of this facet
+ * @param array $options additional option. Please refer to the elasticsearch documentation for details.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-date-histogram-facet.html
+ */
+ public function addDateHistogramFacet($name, $options)
+ {
+ return $this->addFacet($name, 'date_histogram', $options);
+ }
+
+ /**
+ * A filter facet (not to be confused with a facet filter) allows you to return a count of the hits matching the filter.
+ * The filter itself can be expressed using the Query DSL.
+ * @param string $name the name of this facet
+ * @param string $filter the query in Query DSL
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-filter-facet.html
+ */
+ public function addFilterFacet($name, $filter)
+ {
+ return $this->addFacet($name, 'filter', $filter);
+ }
+
+ /**
+ * A facet query allows to return a count of the hits matching the facet query.
+ * The query itself can be expressed using the Query DSL.
+ * @param string $name the name of this facet
+ * @param string $query the query in Query DSL
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-query-facet.html
+ */
+ public function addQueryFacet($name, $query)
+ {
+ return $this->addFacet($name, 'query', $query);
+ }
+
+ /**
+ * Statistical facet allows to compute statistical data on a numeric fields. The statistical data include count,
+ * total, sum of squares, mean (average), minimum, maximum, variance, and standard deviation.
+ * @param string $name the name of this facet
+ * @param array $options additional option. Please refer to the elasticsearch documentation for details.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-statistical-facet.html
+ */
+ public function addStatisticalFacet($name, $options)
+ {
+ return $this->addFacet($name, 'statistical', $options);
+ }
+
+ /**
+ * The `terms_stats` facet combines both the terms and statistical allowing to compute stats computed on a field,
+ * per term value driven by another field.
+ * @param string $name the name of this facet
+ * @param array $options additional option. Please refer to the elasticsearch documentation for details.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-stats-facet.html
+ */
+ public function addTermsStatsFacet($name, $options)
+ {
+ return $this->addFacet($name, 'terms_stats', $options);
+ }
+
+ /**
+ * The `geo_distance` facet is a facet providing information for ranges of distances from a provided `geo_point`
+ * including count of the number of hits that fall within each range, and aggregation information (like `total`).
+ * @param string $name the name of this facet
+ * @param array $options additional option. Please refer to the elasticsearch documentation for details.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-geo-distance-facet.html
+ */
+ public function addGeoDistanceFacet($name, $options)
+ {
+ return $this->addFacet($name, 'geo_distance', $options);
+ }
+
+ // TODO add suggesters http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html
+
+ // TODO add validate query http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-validate.html
+
+ // TODO support multi query via static method http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html
+
+ /**
+ * Sets the querypart of this search query.
+ * @param string $query
+ * @return static the query object itself
+ */
+ public function query($query)
+ {
+ $this->query = $query;
+
+ return $this;
+ }
+
+ /**
+ * Sets the filter part of this search query.
+ * @param string $filter
+ * @return static the query object itself
+ */
+ public function filter($filter)
+ {
+ $this->filter = $filter;
+
+ return $this;
+ }
+
+ /**
+ * Sets the index and type to retrieve documents from.
+ * @param string|array $index The index to retrieve data from. This can be a string representing a single index
+ * or a an array of multiple indexes. If this is `null` it means that all indexes are being queried.
+ * @param string|array $type The type to retrieve data from. This can be a string representing a single type
+ * or a an array of multiple types. If this is `null` it means that all types are being queried.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-search.html#search-multi-index-type
+ */
+ public function from($index, $type = null)
+ {
+ $this->index = $index;
+ $this->type = $type;
+
+ return $this;
+ }
+
+ /**
+ * Sets the fields to retrieve from the documents.
+ * @param array $fields the fields to be selected.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html
+ */
+ public function fields($fields)
+ {
+ if (is_array($fields) || $fields === null) {
+ $this->fields = $fields;
+ } else {
+ $this->fields = func_get_args();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the search timeout.
+ * @param integer $timeout A search timeout, bounding the search request to be executed within the specified time value
+ * and bail with the hits accumulated up to that point when expired. Defaults to no timeout.
+ * @return static the query object itself
+ * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3
+ */
+ public function timeout($timeout)
+ {
+ $this->timeout = $timeout;
+
+ return $this;
+ }
}
diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php
index 0b6a38b06ef..30607ed1ef3 100644
--- a/extensions/elasticsearch/QueryBuilder.php
+++ b/extensions/elasticsearch/QueryBuilder.php
@@ -19,295 +19,302 @@
*/
class QueryBuilder extends \yii\base\Object
{
- /**
- * @var Connection the database connection.
- */
- public $db;
-
- /**
- * Constructor.
- * @param Connection $connection the database connection.
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($connection, $config = [])
- {
- $this->db = $connection;
- parent::__construct($config);
- }
-
- /**
- * Generates query from a [[Query]] object.
- * @param Query $query the [[Query]] object from which the query will be generated
- * @return array the generated SQL statement (the first array element) and the corresponding
- * parameters to be bound to the SQL statement (the second array element).
- */
- public function build($query)
- {
- $parts = [];
-
- if ($query->fields !== null) {
- $parts['fields'] = (array) $query->fields;
- }
- if ($query->limit !== null && $query->limit >= 0) {
- $parts['size'] = $query->limit;
- }
- if ($query->offset > 0) {
- $parts['from'] = (int) $query->offset;
- }
-
- if (empty($query->query)) {
- $parts['query'] = ["match_all" => (object)[]];
- } else {
- $parts['query'] = $query->query;
- }
-
- $whereFilter = $this->buildCondition($query->where);
- if (is_string($query->filter)) {
- if (empty($whereFilter)) {
- $parts['filter'] = $query->filter;
- } else {
- $parts['filter'] = '{"and": [' . $query->filter . ', ' . Json::encode($whereFilter) . ']}';
- }
- } elseif ($query->filter !== null) {
- if (empty($whereFilter)) {
- $parts['filter'] = $query->filter;
- } else {
- $parts['filter'] = ['and' => [$query->filter, $whereFilter]];
- }
- } elseif (!empty($whereFilter)) {
- $parts['filter'] = $whereFilter;
- }
-
- $sort = $this->buildOrderBy($query->orderBy);
- if (!empty($sort)) {
- $parts['sort'] = $sort;
- }
-
- if (!empty($query->facets)) {
- $parts['facets'] = $query->facets;
- }
-
- $options = [];
- if ($query->timeout !== null) {
- $options['timeout'] = $query->timeout;
- }
-
- return [
- 'queryParts' => $parts,
- 'index' => $query->index,
- 'type' => $query->type,
- 'options' => $options,
- ];
- }
-
- /**
- * adds order by condition to the query
- */
- public function buildOrderBy($columns)
- {
- if (empty($columns)) {
- return [];
- }
- $orders = [];
- foreach ($columns as $name => $direction) {
- if (is_string($direction)) {
- $column = $direction;
- $direction = SORT_ASC;
- } else {
- $column = $name;
- }
- if ($column == '_id') {
- $column = '_uid';
- }
-
- // allow elasticsearch extended syntax as described in http://www.elasticsearch.org/guide/reference/api/search/sort/
- if (is_array($direction)) {
- $orders[] = [$column => $direction];
- } else {
- $orders[] = [$column => ($direction === SORT_DESC ? 'desc' : 'asc')];
- }
- }
- return $orders;
- }
-
- /**
- * Parses the condition specification and generates the corresponding SQL expression.
- * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
- * on how to specify a condition.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws \yii\db\Exception if the condition is in bad format
- */
- public function buildCondition($condition)
- {
- static $builders = [
- 'not' => 'buildNotCondition',
- 'and' => 'buildAndCondition',
- 'or' => 'buildAndCondition',
- 'between' => 'buildBetweenCondition',
- 'not between' => 'buildBetweenCondition',
- 'in' => 'buildInCondition',
- 'not in' => 'buildInCondition',
- 'like' => 'buildLikeCondition',
- 'not like' => 'buildLikeCondition',
- 'or like' => 'buildLikeCondition',
- 'or not like' => 'buildLikeCondition',
- ];
-
- if (empty($condition)) {
- return [];
- }
- if (!is_array($condition)) {
- throw new NotSupportedException('String conditions in where() are not supported by elasticsearch.');
- }
- if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
- $operator = strtolower($condition[0]);
- if (isset($builders[$operator])) {
- $method = $builders[$operator];
- array_shift($condition);
- return $this->$method($operator, $condition);
- } else {
- throw new InvalidParamException('Found unknown operator in query: ' . $operator);
- }
- } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
- return $this->buildHashCondition($condition);
- }
- }
-
- private function buildHashCondition($condition)
- {
- $parts = [];
- foreach ($condition as $attribute => $value) {
- if ($attribute == '_id') {
- if ($value == null) { // there is no null pk
- $parts[] = ['script' => ['script' => '0==1']];
- } else {
- $parts[] = ['ids' => ['values' => is_array($value) ? $value : [$value]]];
- }
- } else {
- if (is_array($value)) { // IN condition
- $parts[] = ['in' => [$attribute => $value]];
- } else {
- if ($value === null) {
- $parts[] = ['missing' => ['field' => $attribute, 'existence' => true, 'null_value' => true]];
- } else {
- $parts[] = ['term' => [$attribute => $value]];
- }
- }
- }
- }
- return count($parts) === 1 ? $parts[0] : ['and' => $parts];
- }
-
- private function buildNotCondition($operator, $operands, &$params)
- {
- if (count($operands) != 1) {
- throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
- }
-
- $operand = reset($operands);
- if (is_array($operand)) {
- $operand = $this->buildCondition($operand, $params);
- }
- return [$operator => $operand];
- }
-
- private function buildAndCondition($operator, $operands)
- {
- $parts = [];
- foreach ($operands as $operand) {
- if (is_array($operand)) {
- $operand = $this->buildCondition($operand);
- }
- if (!empty($operand)) {
- $parts[] = $operand;
- }
- }
- if (!empty($parts)) {
- return [$operator => $parts];
- } else {
- return [];
- }
- }
-
- private function buildBetweenCondition($operator, $operands)
- {
- if (!isset($operands[0], $operands[1], $operands[2])) {
- throw new InvalidParamException("Operator '$operator' requires three operands.");
- }
-
- list($column, $value1, $value2) = $operands;
- if ($column == '_id') {
- throw new NotSupportedException('Between condition is not supported for the _id field.');
- }
- $filter = ['range' => [$column => ['gte' => $value1, 'lte' => $value2]]];
- if ($operator == 'not between') {
- $filter = ['not' => $filter];
- }
- return $filter;
- }
-
- private function buildInCondition($operator, $operands)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new InvalidParamException("Operator '$operator' requires two operands.");
- }
-
- list($column, $values) = $operands;
-
- $values = (array)$values;
-
- if (empty($values) || $column === []) {
- return $operator === 'in' ? ['script' => ['script' => '0==1']] : [];
- }
-
- if (count($column) > 1) {
- return $this->buildCompositeInCondition($operator, $column, $values);
- } elseif (is_array($column)) {
- $column = reset($column);
- }
- $canBeNull = false;
- foreach ($values as $i => $value) {
- if (is_array($value)) {
- $values[$i] = $value = isset($value[$column]) ? $value[$column] : null;
- }
- if ($value === null) {
- $canBeNull = true;
- unset($values[$i]);
- }
- }
- if ($column == '_id') {
- if (empty($values) && $canBeNull) { // there is no null pk
- $filter = ['script' => ['script' => '0==1']];
- } else {
- $filter = ['ids' => ['values' => array_values($values)]];
- if ($canBeNull) {
- $filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]];
- }
- }
- } else {
- if (empty($values) && $canBeNull) {
- $filter = ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]];
- } else {
- $filter = ['in' => [$column => array_values($values)]];
- if ($canBeNull) {
- $filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]];
- }
- }
- }
- if ($operator == 'not in') {
- $filter = ['not' => $filter];
- }
- return $filter;
- }
-
- protected function buildCompositeInCondition($operator, $columns, $values)
- {
- throw new NotSupportedException('composite in is not supported by elasticsearch.');
- }
-
- private function buildLikeCondition($operator, $operands)
- {
- throw new NotSupportedException('like conditions are not supported by elasticsearch.');
- }
+ /**
+ * @var Connection the database connection.
+ */
+ public $db;
+
+ /**
+ * Constructor.
+ * @param Connection $connection the database connection.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($connection, $config = [])
+ {
+ $this->db = $connection;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates query from a [[Query]] object.
+ * @param Query $query the [[Query]] object from which the query will be generated
+ * @return array the generated SQL statement (the first array element) and the corresponding
+ * parameters to be bound to the SQL statement (the second array element).
+ */
+ public function build($query)
+ {
+ $parts = [];
+
+ if ($query->fields !== null) {
+ $parts['fields'] = (array) $query->fields;
+ }
+ if ($query->limit !== null && $query->limit >= 0) {
+ $parts['size'] = $query->limit;
+ }
+ if ($query->offset > 0) {
+ $parts['from'] = (int) $query->offset;
+ }
+
+ if (empty($query->query)) {
+ $parts['query'] = ["match_all" => (object) []];
+ } else {
+ $parts['query'] = $query->query;
+ }
+
+ $whereFilter = $this->buildCondition($query->where);
+ if (is_string($query->filter)) {
+ if (empty($whereFilter)) {
+ $parts['filter'] = $query->filter;
+ } else {
+ $parts['filter'] = '{"and": [' . $query->filter . ', ' . Json::encode($whereFilter) . ']}';
+ }
+ } elseif ($query->filter !== null) {
+ if (empty($whereFilter)) {
+ $parts['filter'] = $query->filter;
+ } else {
+ $parts['filter'] = ['and' => [$query->filter, $whereFilter]];
+ }
+ } elseif (!empty($whereFilter)) {
+ $parts['filter'] = $whereFilter;
+ }
+
+ $sort = $this->buildOrderBy($query->orderBy);
+ if (!empty($sort)) {
+ $parts['sort'] = $sort;
+ }
+
+ if (!empty($query->facets)) {
+ $parts['facets'] = $query->facets;
+ }
+
+ $options = [];
+ if ($query->timeout !== null) {
+ $options['timeout'] = $query->timeout;
+ }
+
+ return [
+ 'queryParts' => $parts,
+ 'index' => $query->index,
+ 'type' => $query->type,
+ 'options' => $options,
+ ];
+ }
+
+ /**
+ * adds order by condition to the query
+ */
+ public function buildOrderBy($columns)
+ {
+ if (empty($columns)) {
+ return [];
+ }
+ $orders = [];
+ foreach ($columns as $name => $direction) {
+ if (is_string($direction)) {
+ $column = $direction;
+ $direction = SORT_ASC;
+ } else {
+ $column = $name;
+ }
+ if ($column == '_id') {
+ $column = '_uid';
+ }
+
+ // allow elasticsearch extended syntax as described in http://www.elasticsearch.org/guide/reference/api/search/sort/
+ if (is_array($direction)) {
+ $orders[] = [$column => $direction];
+ } else {
+ $orders[] = [$column => ($direction === SORT_DESC ? 'desc' : 'asc')];
+ }
+ }
+
+ return $orders;
+ }
+
+ /**
+ * Parses the condition specification and generates the corresponding SQL expression.
+ * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
+ * on how to specify a condition.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws \yii\db\Exception if the condition is in bad format
+ */
+ public function buildCondition($condition)
+ {
+ static $builders = [
+ 'not' => 'buildNotCondition',
+ 'and' => 'buildAndCondition',
+ 'or' => 'buildAndCondition',
+ 'between' => 'buildBetweenCondition',
+ 'not between' => 'buildBetweenCondition',
+ 'in' => 'buildInCondition',
+ 'not in' => 'buildInCondition',
+ 'like' => 'buildLikeCondition',
+ 'not like' => 'buildLikeCondition',
+ 'or like' => 'buildLikeCondition',
+ 'or not like' => 'buildLikeCondition',
+ ];
+
+ if (empty($condition)) {
+ return [];
+ }
+ if (!is_array($condition)) {
+ throw new NotSupportedException('String conditions in where() are not supported by elasticsearch.');
+ }
+ if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
+ $operator = strtolower($condition[0]);
+ if (isset($builders[$operator])) {
+ $method = $builders[$operator];
+ array_shift($condition);
+
+ return $this->$method($operator, $condition);
+ } else {
+ throw new InvalidParamException('Found unknown operator in query: ' . $operator);
+ }
+ } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
+
+ return $this->buildHashCondition($condition);
+ }
+ }
+
+ private function buildHashCondition($condition)
+ {
+ $parts = [];
+ foreach ($condition as $attribute => $value) {
+ if ($attribute == '_id') {
+ if ($value == null) { // there is no null pk
+ $parts[] = ['script' => ['script' => '0==1']];
+ } else {
+ $parts[] = ['ids' => ['values' => is_array($value) ? $value : [$value]]];
+ }
+ } else {
+ if (is_array($value)) { // IN condition
+ $parts[] = ['in' => [$attribute => $value]];
+ } else {
+ if ($value === null) {
+ $parts[] = ['missing' => ['field' => $attribute, 'existence' => true, 'null_value' => true]];
+ } else {
+ $parts[] = ['term' => [$attribute => $value]];
+ }
+ }
+ }
+ }
+
+ return count($parts) === 1 ? $parts[0] : ['and' => $parts];
+ }
+
+ private function buildNotCondition($operator, $operands, &$params)
+ {
+ if (count($operands) != 1) {
+ throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
+ }
+
+ $operand = reset($operands);
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($operand, $params);
+ }
+
+ return [$operator => $operand];
+ }
+
+ private function buildAndCondition($operator, $operands)
+ {
+ $parts = [];
+ foreach ($operands as $operand) {
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($operand);
+ }
+ if (!empty($operand)) {
+ $parts[] = $operand;
+ }
+ }
+ if (!empty($parts)) {
+ return [$operator => $parts];
+ } else {
+ return [];
+ }
+ }
+
+ private function buildBetweenCondition($operator, $operands)
+ {
+ if (!isset($operands[0], $operands[1], $operands[2])) {
+ throw new InvalidParamException("Operator '$operator' requires three operands.");
+ }
+
+ list($column, $value1, $value2) = $operands;
+ if ($column == '_id') {
+ throw new NotSupportedException('Between condition is not supported for the _id field.');
+ }
+ $filter = ['range' => [$column => ['gte' => $value1, 'lte' => $value2]]];
+ if ($operator == 'not between') {
+ $filter = ['not' => $filter];
+ }
+
+ return $filter;
+ }
+
+ private function buildInCondition($operator, $operands)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new InvalidParamException("Operator '$operator' requires two operands.");
+ }
+
+ list($column, $values) = $operands;
+
+ $values = (array) $values;
+
+ if (empty($values) || $column === []) {
+ return $operator === 'in' ? ['script' => ['script' => '0==1']] : [];
+ }
+
+ if (count($column) > 1) {
+ return $this->buildCompositeInCondition($operator, $column, $values);
+ } elseif (is_array($column)) {
+ $column = reset($column);
+ }
+ $canBeNull = false;
+ foreach ($values as $i => $value) {
+ if (is_array($value)) {
+ $values[$i] = $value = isset($value[$column]) ? $value[$column] : null;
+ }
+ if ($value === null) {
+ $canBeNull = true;
+ unset($values[$i]);
+ }
+ }
+ if ($column == '_id') {
+ if (empty($values) && $canBeNull) { // there is no null pk
+ $filter = ['script' => ['script' => '0==1']];
+ } else {
+ $filter = ['ids' => ['values' => array_values($values)]];
+ if ($canBeNull) {
+ $filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]];
+ }
+ }
+ } else {
+ if (empty($values) && $canBeNull) {
+ $filter = ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]];
+ } else {
+ $filter = ['in' => [$column => array_values($values)]];
+ if ($canBeNull) {
+ $filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]];
+ }
+ }
+ }
+ if ($operator == 'not in') {
+ $filter = ['not' => $filter];
+ }
+
+ return $filter;
+ }
+
+ protected function buildCompositeInCondition($operator, $columns, $values)
+ {
+ throw new NotSupportedException('composite in is not supported by elasticsearch.');
+ }
+
+ private function buildLikeCondition($operator, $operands)
+ {
+ throw new NotSupportedException('like conditions are not supported by elasticsearch.');
+ }
}
diff --git a/extensions/faker/FixtureController.php b/extensions/faker/FixtureController.php
index befe2617012..b7134a6ea0a 100644
--- a/extensions/faker/FixtureController.php
+++ b/extensions/faker/FixtureController.php
@@ -138,233 +138,236 @@
*/
class FixtureController extends \yii\console\controllers\FixtureController
{
- /**
- * type of fixture generating
- */
- const GENERATE_ALL = 'all';
-
- /**
- * @var string controller default action ID.
- */
- public $defaultAction = 'generate';
- /**
- * @var string Alias to the template path, where all tables templates are stored.
- */
- public $templatePath = '@tests/unit/templates/fixtures';
- /**
- * @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;
- /**
- * @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.
- */
- public $providers = [];
- /**
- * @var \Faker\Generator Faker generator instance
- */
- private $_generator;
-
-
- /**
- * Returns the names of the global options for this command.
- * @return array the names of the global options for this command.
- */
- public function options($id)
- {
- return array_merge(parent::options($id), [
- 'templatePath', 'language', 'fixtureDataPath'
- ]);
- }
-
- public function beforeAction($action)
- {
- if (parent::beforeAction($action)) {
- $this->checkPaths();
- $this->addProviders();
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Generates fixtures and fill them with Faker data.
- * @param string $file filename for the table template. You can generate all fixtures for all tables
- * by specifying keyword "all" as filename.
- * @param integer $times how much fixtures do you want per table
- */
- public function actionGenerate(array $file, $times = 2)
- {
- $templatePath = Yii::getAlias($this->templatePath);
- $fixtureDataPath = Yii::getAlias($this->fixtureDataPath);
-
- if ($this->needToGenerateAll($file[0])) {
- $files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]);
- } else {
- $filesToSearch = [];
- foreach ($file as $fileName) {
- $filesToSearch[] = $fileName . '.php';
- }
- $files = FileHelper::findFiles($templatePath, ['only' => $filesToSearch]);
- }
-
- if (empty($files)) {
- throw new Exception("No files were found by name: \"" . implode(', ', $file) . "\".\n"
- . "Check that template with these name exists, under template path: \n\"{$templatePath}\"."
- );
- }
-
- if (!$this->confirmGeneration($files)) {
- return;
- }
-
- foreach ($files as $templateFile) {
- $fixtureFileName = basename($templateFile);
- $template = $this->getTemplate($templateFile);
- $fixtures = [];
-
- for ($i = 0; $i < $times; $i++) {
- $fixtures[$i] = $this->generateFixture($template, $i);
- }
-
- $content = $this->exportFixtures($fixtures);
- FileHelper::createDirectory($fixtureDataPath);
- file_put_contents($fixtureDataPath . '/'. $fixtureFileName, $content);
-
- $this->stdout("Fixture file was generated under: $fixtureDataPath\n", Console::FG_GREEN);
- }
- }
-
- /**
- * Returns Faker generator instance. Getter for private property.
- * @return \Faker\Generator
- */
- public function getGenerator()
- {
- if (is_null($this->_generator)) {
- //replacing - on _ because Faker support only en_US format and not intl
-
- $language = is_null($this->language) ? str_replace('-', '_', Yii::$app->language) : $this->language;
- $this->_generator = \Faker\Factory::create($language);
- }
-
- return $this->_generator;
- }
-
- /**
- * Check if the template path and migrations path exists and writable.
- */
- public function checkPaths()
- {
- $path = Yii::getAlias($this->templatePath);
-
- if (!is_dir($path)) {
- throw new Exception("The template path \"{$this->templatePath}\" not exist");
- }
- }
-
- /**
- * Adds users providers to the faker generator.
- */
- public function addProviders()
- {
- foreach ($this->providers as $provider) {
- $this->generator->addProvider(new $provider($this->generator));
- }
- }
-
- /**
- * Checks if needed to generate all fixtures.
- * @param string $file
- * @return bool
- */
- public function needToGenerateAll($file)
- {
- return $file == self::GENERATE_ALL;
- }
-
- /**
- * Returns generator template for the given fixture name
- * @param string $file template file
- * @return array generator template
- * @throws \yii\console\Exception if wrong file format
- */
- public function getTemplate($file)
- {
- $template = require($file);
-
- if (!is_array($template)) {
- throw new Exception("The template file \"$file\" has wrong format. It should return valid template array");
- }
-
- return $template;
- }
-
- /**
- * Returns exported to the string representation of given fixtures array.
- * @param array $fixtures
- * @return string exported fixtures format
- */
- public function exportFixtures($fixtures)
- {
- $content = " $value) {
- $content .= "\n\t\t'{$name}' => '{$value}',";
- }
-
- $content .= "\n\t],";
-
- }
- $content .= "\n];\n";
- return $content;
- }
-
- /**
- * Generates fixture from given template
- * @param array $template fixture template
- * @param integer $index current fixture index
- * @return array fixture
- */
- public function generateFixture($template, $index)
- {
- $fixture = [];
-
- foreach ($template as $attribute => $fakerProperty) {
- if (!is_string($fakerProperty)) {
- $fixture = call_user_func_array($fakerProperty, [$fixture, $this->generator, $index]);
- } else {
- $fixture[$attribute] = $this->generator->$fakerProperty;
- }
- }
-
- return $fixture;
- }
-
- /**
- * Prompts user with message if he confirm generation with given fixture templates files.
- * @param array $files
- * @return boolean
- */
- public function confirmGeneration($files)
- {
- $this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW);
- $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("\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);
- }
- return $this->confirm('Generate above fixtures?');
- }
+ /**
+ * type of fixture generating
+ */
+ const GENERATE_ALL = 'all';
+
+ /**
+ * @var string controller default action ID.
+ */
+ public $defaultAction = 'generate';
+ /**
+ * @var string Alias to the template path, where all tables templates are stored.
+ */
+ public $templatePath = '@tests/unit/templates/fixtures';
+ /**
+ * @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;
+ /**
+ * @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.
+ */
+ public $providers = [];
+ /**
+ * @var \Faker\Generator Faker generator instance
+ */
+ private $_generator;
+
+
+ /**
+ * Returns the names of the global options for this command.
+ * @return array the names of the global options for this command.
+ */
+ public function options($id)
+ {
+ return array_merge(parent::options($id), [
+ 'templatePath', 'language', 'fixtureDataPath'
+ ]);
+ }
+
+ public function beforeAction($action)
+ {
+ if (parent::beforeAction($action)) {
+ $this->checkPaths();
+ $this->addProviders();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Generates fixtures and fill them with Faker data.
+ * @param string $file filename for the table template. You can generate all fixtures for all tables
+ * by specifying keyword "all" as filename.
+ * @param integer $times how much fixtures do you want per table
+ */
+ public function actionGenerate(array $file, $times = 2)
+ {
+ $templatePath = Yii::getAlias($this->templatePath);
+ $fixtureDataPath = Yii::getAlias($this->fixtureDataPath);
+
+ if ($this->needToGenerateAll($file[0])) {
+ $files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]);
+ } else {
+ $filesToSearch = [];
+ foreach ($file as $fileName) {
+ $filesToSearch[] = $fileName . '.php';
+ }
+ $files = FileHelper::findFiles($templatePath, ['only' => $filesToSearch]);
+ }
+
+ if (empty($files)) {
+ throw new Exception("No files were found by name: \"" . implode(', ', $file) . "\".\n"
+ . "Check that template with these name exists, under template path: \n\"{$templatePath}\"."
+ );
+ }
+
+ if (!$this->confirmGeneration($files)) {
+ return;
+ }
+
+ foreach ($files as $templateFile) {
+ $fixtureFileName = basename($templateFile);
+ $template = $this->getTemplate($templateFile);
+ $fixtures = [];
+
+ for ($i = 0; $i < $times; $i++) {
+ $fixtures[$i] = $this->generateFixture($template, $i);
+ }
+
+ $content = $this->exportFixtures($fixtures);
+ FileHelper::createDirectory($fixtureDataPath);
+ file_put_contents($fixtureDataPath . '/'. $fixtureFileName, $content);
+
+ $this->stdout("Fixture file was generated under: $fixtureDataPath\n", Console::FG_GREEN);
+ }
+ }
+
+ /**
+ * Returns Faker generator instance. Getter for private property.
+ * @return \Faker\Generator
+ */
+ public function getGenerator()
+ {
+ if (is_null($this->_generator)) {
+ //replacing - on _ because Faker support only en_US format and not intl
+
+ $language = is_null($this->language) ? str_replace('-', '_', Yii::$app->language) : $this->language;
+ $this->_generator = \Faker\Factory::create($language);
+ }
+
+ return $this->_generator;
+ }
+
+ /**
+ * Check if the template path and migrations path exists and writable.
+ */
+ public function checkPaths()
+ {
+ $path = Yii::getAlias($this->templatePath);
+
+ if (!is_dir($path)) {
+ throw new Exception("The template path \"{$this->templatePath}\" not exist");
+ }
+ }
+
+ /**
+ * Adds users providers to the faker generator.
+ */
+ public function addProviders()
+ {
+ foreach ($this->providers as $provider) {
+ $this->generator->addProvider(new $provider($this->generator));
+ }
+ }
+
+ /**
+ * Checks if needed to generate all fixtures.
+ * @param string $file
+ * @return bool
+ */
+ public function needToGenerateAll($file)
+ {
+ return $file == self::GENERATE_ALL;
+ }
+
+ /**
+ * Returns generator template for the given fixture name
+ * @param string $file template file
+ * @return array generator template
+ * @throws \yii\console\Exception if wrong file format
+ */
+ public function getTemplate($file)
+ {
+ $template = require($file);
+
+ if (!is_array($template)) {
+ throw new Exception("The template file \"$file\" has wrong format. It should return valid template array");
+ }
+
+ return $template;
+ }
+
+ /**
+ * Returns exported to the string representation of given fixtures array.
+ * @param array $fixtures
+ * @return string exported fixtures format
+ */
+ public function exportFixtures($fixtures)
+ {
+ $content = " $value) {
+ $content .= "\n\t\t'{$name}' => '{$value}',";
+ }
+
+ $content .= "\n\t],";
+
+ }
+ $content .= "\n];\n";
+
+ return $content;
+ }
+
+ /**
+ * Generates fixture from given template
+ * @param array $template fixture template
+ * @param integer $index current fixture index
+ * @return array fixture
+ */
+ public function generateFixture($template, $index)
+ {
+ $fixture = [];
+
+ foreach ($template as $attribute => $fakerProperty) {
+ if (!is_string($fakerProperty)) {
+ $fixture = call_user_func_array($fakerProperty, [$fixture, $this->generator, $index]);
+ } else {
+ $fixture[$attribute] = $this->generator->$fakerProperty;
+ }
+ }
+
+ return $fixture;
+ }
+
+ /**
+ * Prompts user with message if he confirm generation with given fixture templates files.
+ * @param array $files
+ * @return boolean
+ */
+ public function confirmGeneration($files)
+ {
+ $this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW);
+ $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("\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);
+ }
+
+ return $this->confirm('Generate above fixtures?');
+ }
}
diff --git a/extensions/gii/CodeFile.php b/extensions/gii/CodeFile.php
index 06210cb6280..11e4c2f9cc1 100644
--- a/extensions/gii/CodeFile.php
+++ b/extensions/gii/CodeFile.php
@@ -24,168 +24,170 @@
*/
class CodeFile extends Object
{
- /**
- * The code file is new.
- */
- const OP_CREATE = 'create';
- /**
- * The code file already exists, and the new one may need to overwrite it.
- */
- const OP_OVERWRITE = 'overwrite';
- /**
- * The new code file and the existing one are identical.
- */
- const OP_SKIP = 'skip';
+ /**
+ * The code file is new.
+ */
+ const OP_CREATE = 'create';
+ /**
+ * The code file already exists, and the new one may need to overwrite it.
+ */
+ const OP_OVERWRITE = 'overwrite';
+ /**
+ * The new code file and the existing one are identical.
+ */
+ const OP_SKIP = 'skip';
- /**
- * @var string an ID that uniquely identifies this code file.
- */
- public $id;
- /**
- * @var string the file path that the new code should be saved to.
- */
- public $path;
- /**
- * @var string the newly generated code content
- */
- public $content;
- /**
- * @var string the operation to be performed. This can be [[OP_NEW]], [[OP_OVERWRITE]] or [[OP_SKIP]].
- */
- public $operation;
+ /**
+ * @var string an ID that uniquely identifies this code file.
+ */
+ public $id;
+ /**
+ * @var string the file path that the new code should be saved to.
+ */
+ public $path;
+ /**
+ * @var string the newly generated code content
+ */
+ public $content;
+ /**
+ * @var string the operation to be performed. This can be [[OP_NEW]], [[OP_OVERWRITE]] or [[OP_SKIP]].
+ */
+ public $operation;
- /**
- * Constructor.
- * @param string $path the file path that the new code should be saved to.
- * @param string $content the newly generated code content.
- */
- public function __construct($path, $content)
- {
- $this->path = strtr($path, ['/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]);
- $this->content = $content;
- $this->id = md5($this->path);
- if (is_file($path)) {
- $this->operation = file_get_contents($path) === $content ? self::OP_SKIP : self::OP_OVERWRITE;
- } else {
- $this->operation = self::OP_CREATE;
- }
- }
+ /**
+ * Constructor.
+ * @param string $path the file path that the new code should be saved to.
+ * @param string $content the newly generated code content.
+ */
+ public function __construct($path, $content)
+ {
+ $this->path = strtr($path, ['/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]);
+ $this->content = $content;
+ $this->id = md5($this->path);
+ if (is_file($path)) {
+ $this->operation = file_get_contents($path) === $content ? self::OP_SKIP : self::OP_OVERWRITE;
+ } else {
+ $this->operation = self::OP_CREATE;
+ }
+ }
- /**
- * Saves the code into the file specified by [[path]].
- * @return string|boolean the error occurred while saving the code file, or true if no error.
- */
- public function save()
- {
- $module = Yii::$app->controller->module;
- if ($this->operation === self::OP_CREATE) {
- $dir = dirname($this->path);
- if (!is_dir($dir)) {
- $mask = @umask(0);
- $result = @mkdir($dir, $module->newDirMode, true);
- @umask($mask);
- if (!$result) {
- return "Unable to create the directory '$dir'.";
- }
- }
- }
- if (@file_put_contents($this->path, $this->content) === false) {
- return "Unable to write the file '{$this->path}'.";
- } else {
- $mask = @umask(0);
- @chmod($this->path, $module->newFileMode);
- @umask($mask);
- }
- return true;
- }
+ /**
+ * Saves the code into the file specified by [[path]].
+ * @return string|boolean the error occurred while saving the code file, or true if no error.
+ */
+ public function save()
+ {
+ $module = Yii::$app->controller->module;
+ if ($this->operation === self::OP_CREATE) {
+ $dir = dirname($this->path);
+ if (!is_dir($dir)) {
+ $mask = @umask(0);
+ $result = @mkdir($dir, $module->newDirMode, true);
+ @umask($mask);
+ if (!$result) {
+ return "Unable to create the directory '$dir'.";
+ }
+ }
+ }
+ if (@file_put_contents($this->path, $this->content) === false) {
+ return "Unable to write the file '{$this->path}'.";
+ } else {
+ $mask = @umask(0);
+ @chmod($this->path, $module->newFileMode);
+ @umask($mask);
+ }
- /**
- * @return string the code file path relative to the application base path.
- */
- public function getRelativePath()
- {
- if (strpos($this->path, Yii::$app->basePath) === 0) {
- return substr($this->path, strlen(Yii::$app->basePath) + 1);
- } else {
- return $this->path;
- }
- }
+ return true;
+ }
- /**
- * @return string the code file extension (e.g. php, txt)
- */
- public function getType()
- {
- if (($pos = strrpos($this->path, '.')) !== false) {
- return substr($this->path, $pos + 1);
- } else {
- return 'unknown';
- }
- }
+ /**
+ * @return string the code file path relative to the application base path.
+ */
+ public function getRelativePath()
+ {
+ if (strpos($this->path, Yii::$app->basePath) === 0) {
+ return substr($this->path, strlen(Yii::$app->basePath) + 1);
+ } else {
+ return $this->path;
+ }
+ }
- /**
- * Returns preview or false if it cannot be rendered
- *
- * @return boolean|string
- */
- public function preview()
- {
- if (($pos = strrpos($this->path, '.')) !== false) {
- $type = substr($this->path, $pos + 1);
- } else {
- $type = 'unknown';
- }
+ /**
+ * @return string the code file extension (e.g. php, txt)
+ */
+ public function getType()
+ {
+ if (($pos = strrpos($this->path, '.')) !== false) {
+ return substr($this->path, $pos + 1);
+ } else {
+ return 'unknown';
+ }
+ }
- if ($type === 'php') {
- return highlight_string($this->content, true);
- } elseif (!in_array($type, ['jpg', 'gif', 'png', 'exe'])) {
- return nl2br(Html::encode($this->content));
- } else {
- return false;
- }
- }
+ /**
+ * Returns preview or false if it cannot be rendered
+ *
+ * @return boolean|string
+ */
+ public function preview()
+ {
+ if (($pos = strrpos($this->path, '.')) !== false) {
+ $type = substr($this->path, $pos + 1);
+ } else {
+ $type = 'unknown';
+ }
- /**
- * Returns diff or false if it cannot be calculated
- *
- * @return boolean|string
- */
- public function diff()
- {
- $type = strtolower($this->getType());
- if (in_array($type, ['jpg', 'gif', 'png', 'exe'])) {
- return false;
- } elseif ($this->operation === self::OP_OVERWRITE) {
- return $this->renderDiff(file($this->path), $this->content);
- } else {
- return '';
- }
- }
+ if ($type === 'php') {
+ return highlight_string($this->content, true);
+ } elseif (!in_array($type, ['jpg', 'gif', 'png', 'exe'])) {
+ return nl2br(Html::encode($this->content));
+ } else {
+ return false;
+ }
+ }
- /**
- * Renders diff between two sets of lines
- *
- * @param mixed $lines1
- * @param mixed $lines2
- * @return string
- */
- private function renderDiff($lines1, $lines2)
- {
- if (!is_array($lines1)) {
- $lines1 = explode("\n", $lines1);
- }
- if (!is_array($lines2)) {
- $lines2 = explode("\n", $lines2);
- }
- foreach ($lines1 as $i => $line) {
- $lines1[$i] = rtrim($line, "\r\n");
- }
- foreach ($lines2 as $i => $line) {
- $lines2[$i] = rtrim($line, "\r\n");
- }
+ /**
+ * Returns diff or false if it cannot be calculated
+ *
+ * @return boolean|string
+ */
+ public function diff()
+ {
+ $type = strtolower($this->getType());
+ if (in_array($type, ['jpg', 'gif', 'png', 'exe'])) {
+ return false;
+ } elseif ($this->operation === self::OP_OVERWRITE) {
+ return $this->renderDiff(file($this->path), $this->content);
+ } else {
+ return '';
+ }
+ }
- $renderer = new DiffRendererHtmlInline();
- $diff = new \Diff($lines1, $lines2);
- return $diff->render($renderer);
- }
+ /**
+ * Renders diff between two sets of lines
+ *
+ * @param mixed $lines1
+ * @param mixed $lines2
+ * @return string
+ */
+ private function renderDiff($lines1, $lines2)
+ {
+ if (!is_array($lines1)) {
+ $lines1 = explode("\n", $lines1);
+ }
+ if (!is_array($lines2)) {
+ $lines2 = explode("\n", $lines2);
+ }
+ foreach ($lines1 as $i => $line) {
+ $lines1[$i] = rtrim($line, "\r\n");
+ }
+ foreach ($lines2 as $i => $line) {
+ $lines2[$i] = rtrim($line, "\r\n");
+ }
+
+ $renderer = new DiffRendererHtmlInline();
+ $diff = new \Diff($lines1, $lines2);
+
+ return $diff->render($renderer);
+ }
}
diff --git a/extensions/gii/Generator.php b/extensions/gii/Generator.php
index 9dc3c6b5f40..794a0843ff1 100644
--- a/extensions/gii/Generator.php
+++ b/extensions/gii/Generator.php
@@ -37,411 +37,415 @@
*/
abstract class Generator extends Model
{
- /**
- * @var array a list of available code templates. The array keys are the template names,
- * and the array values are the corresponding template paths or path aliases.
- */
- public $templates = [];
- /**
- * @var string the name of the code template that the user has selected.
- * The value of this property is internally managed by this class.
- */
- public $template;
+ /**
+ * @var array a list of available code templates. The array keys are the template names,
+ * and the array values are the corresponding template paths or path aliases.
+ */
+ public $templates = [];
+ /**
+ * @var string the name of the code template that the user has selected.
+ * The value of this property is internally managed by this class.
+ */
+ public $template;
- /**
- * @return string name of the code generator
- */
- abstract public function getName();
- /**
- * Generates the code based on the current user input and the specified code template files.
- * This is the main method that child classes should implement.
- * Please refer to [[\yii\gii\generators\controller\Generator::generate()]] as an example
- * on how to implement this method.
- * @return CodeFile[] a list of code files to be created.
- */
- abstract public function generate();
+ /**
+ * @return string name of the code generator
+ */
+ abstract public function getName();
+ /**
+ * Generates the code based on the current user input and the specified code template files.
+ * This is the main method that child classes should implement.
+ * Please refer to [[\yii\gii\generators\controller\Generator::generate()]] as an example
+ * on how to implement this method.
+ * @return CodeFile[] a list of code files to be created.
+ */
+ abstract public function generate();
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if (!isset($this->templates['default'])) {
- $this->templates['default'] = $this->defaultTemplate();
- }
- foreach ($this->templates as $i => $template) {
- $this->templates[$i] = Yii::getAlias($template);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->templates['default'])) {
+ $this->templates['default'] = $this->defaultTemplate();
+ }
+ foreach ($this->templates as $i => $template) {
+ $this->templates[$i] = Yii::getAlias($template);
+ }
+ }
- /**
- * Returns a list of code template files that are required.
- * Derived classes usually should override this method if they require the existence of
- * certain template files.
- * @return array list of code template files that are required. They should be file paths
- * relative to [[templatePath]].
- */
- public function requiredTemplates()
- {
- return [];
- }
+ /**
+ * Returns a list of code template files that are required.
+ * Derived classes usually should override this method if they require the existence of
+ * certain template files.
+ * @return array list of code template files that are required. They should be file paths
+ * relative to [[templatePath]].
+ */
+ public function requiredTemplates()
+ {
+ return [];
+ }
- /**
- * Returns the list of sticky attributes.
- * A sticky attribute will remember its value and will initialize the attribute with this value
- * when the generator is restarted.
- * @return array list of sticky attributes
- */
- public function stickyAttributes()
- {
- return ['template'];
- }
+ /**
+ * Returns the list of sticky attributes.
+ * A sticky attribute will remember its value and will initialize the attribute with this value
+ * when the generator is restarted.
+ * @return array list of sticky attributes
+ */
+ public function stickyAttributes()
+ {
+ return ['template'];
+ }
- /**
- * Returns the list of hint messages.
- * The array keys are the attribute names, and the array values are the corresponding hint messages.
- * Hint messages will be displayed to end users when they are filling the form for the generator.
- * @return array the list of hint messages
- */
- public function hints()
- {
- return [];
- }
+ /**
+ * Returns the list of hint messages.
+ * The array keys are the attribute names, and the array values are the corresponding hint messages.
+ * Hint messages will be displayed to end users when they are filling the form for the generator.
+ * @return array the list of hint messages
+ */
+ public function hints()
+ {
+ return [];
+ }
- /**
- * Returns the list of auto complete values.
- * The array keys are the attribute names, and the array values are the corresponding auto complete values.
- * Auto complete values can also be callable typed in order one want to make postponed data generation.
- * @return array the list of auto complete values
- */
- public function autoCompleteData()
- {
- return [];
- }
+ /**
+ * Returns the list of auto complete values.
+ * The array keys are the attribute names, and the array values are the corresponding auto complete values.
+ * Auto complete values can also be callable typed in order one want to make postponed data generation.
+ * @return array the list of auto complete values
+ */
+ public function autoCompleteData()
+ {
+ return [];
+ }
- /**
- * Returns the message to be displayed when the newly generated code is saved successfully.
- * Child classes may override this method to customize the message.
- * @return string the message to be displayed when the newly generated code is saved successfully.
- */
- public function successMessage()
- {
- return 'The code has been generated successfully.';
- }
+ /**
+ * Returns the message to be displayed when the newly generated code is saved successfully.
+ * Child classes may override this method to customize the message.
+ * @return string the message to be displayed when the newly generated code is saved successfully.
+ */
+ public function successMessage()
+ {
+ return 'The code has been generated successfully.';
+ }
- /**
- * Returns the view file for the input form of the generator.
- * The default implementation will return the "form.php" file under the directory
- * that contains the generator class file.
- * @return string the view file for the input form of the generator.
- */
- public function formView()
- {
- $class = new ReflectionClass($this);
- return dirname($class->getFileName()) . '/form.php';
- }
+ /**
+ * Returns the view file for the input form of the generator.
+ * The default implementation will return the "form.php" file under the directory
+ * that contains the generator class file.
+ * @return string the view file for the input form of the generator.
+ */
+ public function formView()
+ {
+ $class = new ReflectionClass($this);
- /**
- * Returns the root path to the default code template files.
- * The default implementation will return the "templates" subdirectory of the
- * directory containing the generator class file.
- * @return string the root path to the default code template files.
- */
- public function defaultTemplate()
- {
- $class = new ReflectionClass($this);
- return dirname($class->getFileName()) . '/templates';
- }
+ return dirname($class->getFileName()) . '/form.php';
+ }
- /**
- * @return string the detailed description of the generator.
- */
- public function getDescription()
- {
- return '';
- }
+ /**
+ * Returns the root path to the default code template files.
+ * The default implementation will return the "templates" subdirectory of the
+ * directory containing the generator class file.
+ * @return string the root path to the default code template files.
+ */
+ public function defaultTemplate()
+ {
+ $class = new ReflectionClass($this);
- /**
- * @inheritdoc
- *
- * Child classes should override this method like the following so that the parent
- * rules are included:
- *
- * ~~~
- * return array_merge(parent::rules(), [
- * ...rules for the child class...
- * ]);
- * ~~~
- */
- public function rules()
- {
- return [
- [['template'], 'required', 'message' => 'A code template must be selected.'],
- [['template'], 'validateTemplate'],
- ];
- }
+ return dirname($class->getFileName()) . '/templates';
+ }
- /**
- * Loads sticky attributes from an internal file and populates them into the generator.
- * @internal
- */
- public function loadStickyAttributes()
- {
- $stickyAttributes = $this->stickyAttributes();
- $path = $this->getStickyDataFile();
- if (is_file($path)) {
- $result = json_decode(file_get_contents($path), true);
- if (is_array($result)) {
- foreach ($stickyAttributes as $name) {
- if (isset($result[$name])) {
- $this->$name = $result[$name];
- }
- }
- }
- }
- }
+ /**
+ * @return string the detailed description of the generator.
+ */
+ public function getDescription()
+ {
+ return '';
+ }
- /**
- * Saves sticky attributes into an internal file.
- * @internal
- */
- public function saveStickyAttributes()
- {
- $stickyAttributes = $this->stickyAttributes();
- $stickyAttributes[] = 'template';
- $values = [];
- foreach ($stickyAttributes as $name) {
- $values[$name] = $this->$name;
- }
- $path = $this->getStickyDataFile();
- @mkdir(dirname($path), 0755, true);
- file_put_contents($path, json_encode($values));
- }
+ /**
+ * @inheritdoc
+ *
+ * Child classes should override this method like the following so that the parent
+ * rules are included:
+ *
+ * ~~~
+ * return array_merge(parent::rules(), [
+ * ...rules for the child class...
+ * ]);
+ * ~~~
+ */
+ public function rules()
+ {
+ return [
+ [['template'], 'required', 'message' => 'A code template must be selected.'],
+ [['template'], 'validateTemplate'],
+ ];
+ }
- /**
- * @return string the file path that stores the sticky attribute values.
- * @internal
- */
- public function getStickyDataFile()
- {
- return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.json';
- }
+ /**
+ * Loads sticky attributes from an internal file and populates them into the generator.
+ * @internal
+ */
+ public function loadStickyAttributes()
+ {
+ $stickyAttributes = $this->stickyAttributes();
+ $path = $this->getStickyDataFile();
+ if (is_file($path)) {
+ $result = json_decode(file_get_contents($path), true);
+ if (is_array($result)) {
+ foreach ($stickyAttributes as $name) {
+ if (isset($result[$name])) {
+ $this->$name = $result[$name];
+ }
+ }
+ }
+ }
+ }
- /**
- * Saves the generated code into files.
- * @param CodeFile[] $files the code files to be saved
- * @param array $answers
- * @param string $results this parameter receives a value from this method indicating the log messages
- * generated while saving the code files.
- * @return boolean whether there is any error while saving the code files.
- */
- public function save($files, $answers, &$results)
- {
- $lines = ['Generating code using template "' . $this->getTemplatePath() . '"...'];
- $hasError = false;
- foreach ($files as $file) {
- $relativePath = $file->getRelativePath();
- if (isset($answers[$file->id]) && $file->operation !== CodeFile::OP_SKIP) {
- $error = $file->save();
- if (is_string($error)) {
- $hasError = true;
- $lines[] = "generating $relativePath\n$error";
- } else {
- $lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath";
- }
- } else {
- $lines[] = " skipped $relativePath";
- }
- }
- $lines[] = "done!\n";
- $results = implode("\n", $lines);
+ /**
+ * Saves sticky attributes into an internal file.
+ * @internal
+ */
+ public function saveStickyAttributes()
+ {
+ $stickyAttributes = $this->stickyAttributes();
+ $stickyAttributes[] = 'template';
+ $values = [];
+ foreach ($stickyAttributes as $name) {
+ $values[$name] = $this->$name;
+ }
+ $path = $this->getStickyDataFile();
+ @mkdir(dirname($path), 0755, true);
+ file_put_contents($path, json_encode($values));
+ }
- return $hasError;
- }
+ /**
+ * @return string the file path that stores the sticky attribute values.
+ * @internal
+ */
+ public function getStickyDataFile()
+ {
+ return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.json';
+ }
- /**
- * @return string the root path of the template files that are currently being used.
- * @throws InvalidConfigException if [[template]] is invalid
- */
- public function getTemplatePath()
- {
- if (isset($this->templates[$this->template])) {
- return $this->templates[$this->template];
- } else {
- throw new InvalidConfigException("Unknown template: {$this->template}");
- }
- }
+ /**
+ * Saves the generated code into files.
+ * @param CodeFile[] $files the code files to be saved
+ * @param array $answers
+ * @param string $results this parameter receives a value from this method indicating the log messages
+ * generated while saving the code files.
+ * @return boolean whether there is any error while saving the code files.
+ */
+ public function save($files, $answers, &$results)
+ {
+ $lines = ['Generating code using template "' . $this->getTemplatePath() . '"...'];
+ $hasError = false;
+ foreach ($files as $file) {
+ $relativePath = $file->getRelativePath();
+ if (isset($answers[$file->id]) && $file->operation !== CodeFile::OP_SKIP) {
+ $error = $file->save();
+ if (is_string($error)) {
+ $hasError = true;
+ $lines[] = "generating $relativePath\n$error";
+ } else {
+ $lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath";
+ }
+ } else {
+ $lines[] = " skipped $relativePath";
+ }
+ }
+ $lines[] = "done!\n";
+ $results = implode("\n", $lines);
- /**
- * Generates code using the specified code template and parameters.
- * Note that the code template will be used as a PHP file.
- * @param string $template the code template file. This must be specified as a file path
- * relative to [[templatePath]].
- * @param array $params list of parameters to be passed to the template file.
- * @return string the generated code
- */
- public function render($template, $params = [])
- {
- $view = new View;
- $params['generator'] = $this;
- return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this);
- }
+ return $hasError;
+ }
- /**
- * Validates the template selection.
- * This method validates whether the user selects an existing template
- * and the template contains all required template files as specified in [[requiredTemplates()]].
- */
- public function validateTemplate()
- {
- $templates = $this->templates;
- if (!isset($templates[$this->template])) {
- $this->addError('template', 'Invalid template selection.');
- } else {
- $templatePath = $this->templates[$this->template];
- foreach ($this->requiredTemplates() as $template) {
- if (!is_file($templatePath . '/' . $template)) {
- $this->addError('template', "Unable to find the required code template file '$template'.");
- }
- }
- }
- }
+ /**
+ * @return string the root path of the template files that are currently being used.
+ * @throws InvalidConfigException if [[template]] is invalid
+ */
+ public function getTemplatePath()
+ {
+ if (isset($this->templates[$this->template])) {
+ return $this->templates[$this->template];
+ } else {
+ throw new InvalidConfigException("Unknown template: {$this->template}");
+ }
+ }
- /**
- * An inline validator that checks if the attribute value refers to an existing class name.
- * If the `extends` option is specified, it will also check if the class is a child class
- * of the class represented by the `extends` option.
- * @param string $attribute the attribute being validated
- * @param array $params the validation options
- */
- public function validateClass($attribute, $params)
- {
- $class = $this->$attribute;
- try {
- if (class_exists($class)) {
- if (isset($params['extends'])) {
- if (ltrim($class, '\\') !== ltrim($params['extends'], '\\') && !is_subclass_of($class, $params['extends'])) {
- $this->addError($attribute, "'$class' must extend from {$params['extends']} or its child class.");
- }
- }
- } else {
- $this->addError($attribute, "Class '$class' does not exist or has syntax error.");
- }
- } catch (\Exception $e) {
- $this->addError($attribute, "Class '$class' does not exist or has syntax error.");
- }
- }
+ /**
+ * Generates code using the specified code template and parameters.
+ * Note that the code template will be used as a PHP file.
+ * @param string $template the code template file. This must be specified as a file path
+ * relative to [[templatePath]].
+ * @param array $params list of parameters to be passed to the template file.
+ * @return string the generated code
+ */
+ public function render($template, $params = [])
+ {
+ $view = new View;
+ $params['generator'] = $this;
- /**
- * An inline validator that checks if the attribute value refers to a valid namespaced class name.
- * The validator will check if the directory containing the new class file exist or not.
- * @param string $attribute the attribute being validated
- * @param array $params the validation options
- */
- public function validateNewClass($attribute, $params)
- {
- $class = ltrim($this->$attribute, '\\');
- if (($pos = strrpos($class, '\\')) === false) {
- $this->addError($attribute, "The class name must contain fully qualified namespace name.");
- } else {
- $ns = substr($class, 0, $pos);
- $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false);
- if ($path === false) {
- $this->addError($attribute, "The class namespace is invalid: $ns");
- } elseif (!is_dir($path)) {
- $this->addError($attribute, "Please make sure the directory containing this class exists: $path");
- }
- }
- }
+ return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this);
+ }
- /**
- * @param string $value the attribute to be validated
- * @return boolean whether the value is a reserved PHP keyword.
- */
- public function isReservedKeyword($value)
- {
- static $keywords = [
- '__class__',
- '__dir__',
- '__file__',
- '__function__',
- '__line__',
- '__method__',
- '__namespace__',
- '__trait__',
- 'abstract',
- 'and',
- 'array',
- 'as',
- 'break',
- 'case',
- 'catch',
- 'callable',
- 'cfunction',
- 'class',
- 'clone',
- 'const',
- 'continue',
- 'declare',
- 'default',
- 'die',
- 'do',
- 'echo',
- 'else',
- 'elseif',
- 'empty',
- 'enddeclare',
- 'endfor',
- 'endforeach',
- 'endif',
- 'endswitch',
- 'endwhile',
- 'eval',
- 'exception',
- 'exit',
- 'extends',
- 'final',
- 'finally',
- 'for',
- 'foreach',
- 'function',
- 'global',
- 'goto',
- 'if',
- 'implements',
- 'include',
- 'include_once',
- 'instanceof',
- 'insteadof',
- 'interface',
- 'isset',
- 'list',
- 'namespace',
- 'new',
- 'old_function',
- 'or',
- 'parent',
- 'php_user_filter',
- 'print',
- 'private',
- 'protected',
- 'public',
- 'require',
- 'require_once',
- 'return',
- 'static',
- 'switch',
- 'this',
- 'throw',
- 'trait',
- 'try',
- 'unset',
- 'use',
- 'var',
- 'while',
- 'xor',
- ];
- return in_array(strtolower($value), $keywords, true);
- }
+ /**
+ * Validates the template selection.
+ * This method validates whether the user selects an existing template
+ * and the template contains all required template files as specified in [[requiredTemplates()]].
+ */
+ public function validateTemplate()
+ {
+ $templates = $this->templates;
+ if (!isset($templates[$this->template])) {
+ $this->addError('template', 'Invalid template selection.');
+ } else {
+ $templatePath = $this->templates[$this->template];
+ foreach ($this->requiredTemplates() as $template) {
+ if (!is_file($templatePath . '/' . $template)) {
+ $this->addError('template', "Unable to find the required code template file '$template'.");
+ }
+ }
+ }
+ }
+
+ /**
+ * An inline validator that checks if the attribute value refers to an existing class name.
+ * If the `extends` option is specified, it will also check if the class is a child class
+ * of the class represented by the `extends` option.
+ * @param string $attribute the attribute being validated
+ * @param array $params the validation options
+ */
+ public function validateClass($attribute, $params)
+ {
+ $class = $this->$attribute;
+ try {
+ if (class_exists($class)) {
+ if (isset($params['extends'])) {
+ if (ltrim($class, '\\') !== ltrim($params['extends'], '\\') && !is_subclass_of($class, $params['extends'])) {
+ $this->addError($attribute, "'$class' must extend from {$params['extends']} or its child class.");
+ }
+ }
+ } else {
+ $this->addError($attribute, "Class '$class' does not exist or has syntax error.");
+ }
+ } catch (\Exception $e) {
+ $this->addError($attribute, "Class '$class' does not exist or has syntax error.");
+ }
+ }
+
+ /**
+ * An inline validator that checks if the attribute value refers to a valid namespaced class name.
+ * The validator will check if the directory containing the new class file exist or not.
+ * @param string $attribute the attribute being validated
+ * @param array $params the validation options
+ */
+ public function validateNewClass($attribute, $params)
+ {
+ $class = ltrim($this->$attribute, '\\');
+ if (($pos = strrpos($class, '\\')) === false) {
+ $this->addError($attribute, "The class name must contain fully qualified namespace name.");
+ } else {
+ $ns = substr($class, 0, $pos);
+ $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false);
+ if ($path === false) {
+ $this->addError($attribute, "The class namespace is invalid: $ns");
+ } elseif (!is_dir($path)) {
+ $this->addError($attribute, "Please make sure the directory containing this class exists: $path");
+ }
+ }
+ }
+
+ /**
+ * @param string $value the attribute to be validated
+ * @return boolean whether the value is a reserved PHP keyword.
+ */
+ public function isReservedKeyword($value)
+ {
+ static $keywords = [
+ '__class__',
+ '__dir__',
+ '__file__',
+ '__function__',
+ '__line__',
+ '__method__',
+ '__namespace__',
+ '__trait__',
+ 'abstract',
+ 'and',
+ 'array',
+ 'as',
+ 'break',
+ 'case',
+ 'catch',
+ 'callable',
+ 'cfunction',
+ 'class',
+ 'clone',
+ 'const',
+ 'continue',
+ 'declare',
+ 'default',
+ 'die',
+ 'do',
+ 'echo',
+ 'else',
+ 'elseif',
+ 'empty',
+ 'enddeclare',
+ 'endfor',
+ 'endforeach',
+ 'endif',
+ 'endswitch',
+ 'endwhile',
+ 'eval',
+ 'exception',
+ 'exit',
+ 'extends',
+ 'final',
+ 'finally',
+ 'for',
+ 'foreach',
+ 'function',
+ 'global',
+ 'goto',
+ 'if',
+ 'implements',
+ 'include',
+ 'include_once',
+ 'instanceof',
+ 'insteadof',
+ 'interface',
+ 'isset',
+ 'list',
+ 'namespace',
+ 'new',
+ 'old_function',
+ 'or',
+ 'parent',
+ 'php_user_filter',
+ 'print',
+ 'private',
+ 'protected',
+ 'public',
+ 'require',
+ 'require_once',
+ 'return',
+ 'static',
+ 'switch',
+ 'this',
+ 'throw',
+ 'trait',
+ 'try',
+ 'unset',
+ 'use',
+ 'var',
+ 'while',
+ 'xor',
+ ];
+
+ return in_array(strtolower($value), $keywords, true);
+ }
}
diff --git a/extensions/gii/GiiAsset.php b/extensions/gii/GiiAsset.php
index b100750f0ba..3eeb6734259 100644
--- a/extensions/gii/GiiAsset.php
+++ b/extensions/gii/GiiAsset.php
@@ -17,30 +17,30 @@
*/
class GiiAsset extends AssetBundle
{
- /**
- * @inheritdoc
- */
- public $sourcePath = '@yii/gii/assets';
- /**
- * @inheritdoc
- */
- public $css = [
- 'main.css',
- 'typeahead.js-bootstrap.css',
- ];
- /**
- * @inheritdoc
- */
- public $js = [
- 'gii.js',
- 'typeahead.js',
- ];
- /**
- * @inheritdoc
- */
- public $depends = [
- 'yii\web\YiiAsset',
- 'yii\bootstrap\BootstrapAsset',
- 'yii\bootstrap\BootstrapPluginAsset',
- ];
+ /**
+ * @inheritdoc
+ */
+ public $sourcePath = '@yii/gii/assets';
+ /**
+ * @inheritdoc
+ */
+ public $css = [
+ 'main.css',
+ 'typeahead.js-bootstrap.css',
+ ];
+ /**
+ * @inheritdoc
+ */
+ public $js = [
+ 'gii.js',
+ 'typeahead.js',
+ ];
+ /**
+ * @inheritdoc
+ */
+ public $depends = [
+ 'yii\web\YiiAsset',
+ 'yii\bootstrap\BootstrapAsset',
+ 'yii\bootstrap\BootstrapPluginAsset',
+ ];
}
diff --git a/extensions/gii/Module.php b/extensions/gii/Module.php
index 0b0f6e4d6e1..b6936b036ce 100644
--- a/extensions/gii/Module.php
+++ b/extensions/gii/Module.php
@@ -53,95 +53,95 @@
*/
class Module extends \yii\base\Module
{
- /**
- * @inheritdoc
- */
- public $controllerNamespace = 'yii\gii\controllers';
- /**
- * @var array the list of IPs that are allowed to access this module.
- * Each array element represents a single IP filter which can be either an IP address
- * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment.
- * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed
- * by localhost.
- */
- public $allowedIPs = ['127.0.0.1', '::1'];
- /**
- * @var array|Generator[] a list of generator configurations or instances. The array keys
- * are the generator IDs (e.g. "crud"), and the array elements are the corresponding generator
- * configurations or the instances.
- *
- * After the module is initialized, this property will become an array of generator instances
- * which are created based on the configurations previously taken by this property.
- *
- * Newly assigned generators will be merged with the [[coreGenerators()|core ones]], and the former
- * takes precedence in case when they have the same generator ID.
- */
- public $generators = [];
- /**
- * @var integer the permission to be set for newly generated code files.
- * This value will be used by PHP chmod function.
- * Defaults to 0666, meaning the file is read-writable by all users.
- */
- public $newFileMode = 0666;
- /**
- * @var integer the permission to be set for newly generated directories.
- * This value will be used by PHP chmod function.
- * Defaults to 0777, meaning the directory can be read, written and executed by all users.
- */
- public $newDirMode = 0777;
+ /**
+ * @inheritdoc
+ */
+ public $controllerNamespace = 'yii\gii\controllers';
+ /**
+ * @var array the list of IPs that are allowed to access this module.
+ * Each array element represents a single IP filter which can be either an IP address
+ * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment.
+ * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed
+ * by localhost.
+ */
+ public $allowedIPs = ['127.0.0.1', '::1'];
+ /**
+ * @var array|Generator[] a list of generator configurations or instances. The array keys
+ * are the generator IDs (e.g. "crud"), and the array elements are the corresponding generator
+ * configurations or the instances.
+ *
+ * After the module is initialized, this property will become an array of generator instances
+ * which are created based on the configurations previously taken by this property.
+ *
+ * Newly assigned generators will be merged with the [[coreGenerators()|core ones]], and the former
+ * takes precedence in case when they have the same generator ID.
+ */
+ public $generators = [];
+ /**
+ * @var integer the permission to be set for newly generated code files.
+ * This value will be used by PHP chmod function.
+ * Defaults to 0666, meaning the file is read-writable by all users.
+ */
+ public $newFileMode = 0666;
+ /**
+ * @var integer the permission to be set for newly generated directories.
+ * This value will be used by PHP chmod function.
+ * Defaults to 0777, meaning the directory can be read, written and executed by all users.
+ */
+ public $newDirMode = 0777;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ foreach (array_merge($this->coreGenerators(), $this->generators) as $id => $config) {
+ $this->generators[$id] = Yii::createObject($config);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- foreach (array_merge($this->coreGenerators(), $this->generators) as $id => $config) {
- $this->generators[$id] = Yii::createObject($config);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function beforeAction($action)
+ {
+ if ($this->checkAccess()) {
+ return parent::beforeAction($action);
+ } else {
+ throw new ForbiddenHttpException('You are not allowed to access this page.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function beforeAction($action)
- {
- if ($this->checkAccess()) {
- return parent::beforeAction($action);
- } else {
- throw new ForbiddenHttpException('You are not allowed to access this page.');
- }
- }
+ /**
+ * @return boolean whether the module can be accessed by the current user
+ */
+ protected function checkAccess()
+ {
+ $ip = Yii::$app->getRequest()->getUserIP();
+ foreach ($this->allowedIPs as $filter) {
+ if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) {
+ return true;
+ }
+ }
+ Yii::warning('Access to Gii is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__);
- /**
- * @return boolean whether the module can be accessed by the current user
- */
- protected function checkAccess()
- {
- $ip = Yii::$app->getRequest()->getUserIP();
- foreach ($this->allowedIPs as $filter) {
- if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) {
- return true;
- }
- }
- Yii::warning('Access to Gii is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__);
- return false;
- }
+ return false;
+ }
- /**
- * Returns the list of the core code generator configurations.
- * @return array the list of the core code generator configurations.
- */
- protected function coreGenerators()
- {
- return [
- 'model' => ['class' => 'yii\gii\generators\model\Generator'],
- 'crud' => ['class' => 'yii\gii\generators\crud\Generator'],
- 'controller' => ['class' => 'yii\gii\generators\controller\Generator'],
- 'form' => ['class' => 'yii\gii\generators\form\Generator'],
- 'module' => ['class' => 'yii\gii\generators\module\Generator'],
- 'extension' => ['class' => 'yii\gii\generators\extension\Generator'],
- ];
- }
+ /**
+ * Returns the list of the core code generator configurations.
+ * @return array the list of the core code generator configurations.
+ */
+ protected function coreGenerators()
+ {
+ return [
+ 'model' => ['class' => 'yii\gii\generators\model\Generator'],
+ 'crud' => ['class' => 'yii\gii\generators\crud\Generator'],
+ 'controller' => ['class' => 'yii\gii\generators\controller\Generator'],
+ 'form' => ['class' => 'yii\gii\generators\form\Generator'],
+ 'module' => ['class' => 'yii\gii\generators\module\Generator'],
+ 'extension' => ['class' => 'yii\gii\generators\extension\Generator'],
+ ];
+ }
}
diff --git a/extensions/gii/components/ActiveField.php b/extensions/gii/components/ActiveField.php
index d128aa28d4b..13fc8289540 100644
--- a/extensions/gii/components/ActiveField.php
+++ b/extensions/gii/components/ActiveField.php
@@ -16,57 +16,59 @@
*/
class ActiveField extends \yii\widgets\ActiveField
{
- /**
- * @var Generator
- */
- public $model;
+ /**
+ * @var Generator
+ */
+ public $model;
- /**
- * @inheritdoc
- */
- public function init()
- {
- $stickyAttributes = $this->model->stickyAttributes();
- if (in_array($this->attribute, $stickyAttributes)) {
- $this->sticky();
- }
- $hints = $this->model->hints();
- if (isset($hints[$this->attribute])) {
- $this->hint($hints[$this->attribute]);
- }
- $autoCompleteData = $this->model->autoCompleteData();
- if (isset($autoCompleteData[$this->attribute])) {
- if (is_callable($autoCompleteData[$this->attribute])) {
- $this->autoComplete(call_user_func($autoCompleteData[$this->attribute]));
- } else {
- $this->autoComplete($autoCompleteData[$this->attribute]);
- }
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ $stickyAttributes = $this->model->stickyAttributes();
+ if (in_array($this->attribute, $stickyAttributes)) {
+ $this->sticky();
+ }
+ $hints = $this->model->hints();
+ if (isset($hints[$this->attribute])) {
+ $this->hint($hints[$this->attribute]);
+ }
+ $autoCompleteData = $this->model->autoCompleteData();
+ if (isset($autoCompleteData[$this->attribute])) {
+ if (is_callable($autoCompleteData[$this->attribute])) {
+ $this->autoComplete(call_user_func($autoCompleteData[$this->attribute]));
+ } else {
+ $this->autoComplete($autoCompleteData[$this->attribute]);
+ }
+ }
+ }
- /**
- * Makes field remember its value between page reloads
- * @return static the field object itself
- */
- public function sticky()
- {
- $this->options['class'] .= ' sticky';
- return $this;
- }
+ /**
+ * Makes field remember its value between page reloads
+ * @return static the field object itself
+ */
+ public function sticky()
+ {
+ $this->options['class'] .= ' sticky';
- /**
- * Makes field auto completable
- * @param array $data auto complete data (array of callables or scalars)
- * @return static the field object itself
- */
- public function autoComplete($data)
- {
- static $counter = 0;
- $this->inputOptions['class'] .= ' typeahead-' . (++$counter);
- foreach ($data as &$item) {
- $item = ['word' => $item];
- }
- $this->form->getView()->registerJs("yii.gii.autocomplete($counter, " . Json::encode($data) . ");");
- return $this;
- }
+ return $this;
+ }
+
+ /**
+ * Makes field auto completable
+ * @param array $data auto complete data (array of callables or scalars)
+ * @return static the field object itself
+ */
+ public function autoComplete($data)
+ {
+ static $counter = 0;
+ $this->inputOptions['class'] .= ' typeahead-' . (++$counter);
+ foreach ($data as &$item) {
+ $item = ['word' => $item];
+ }
+ $this->form->getView()->registerJs("yii.gii.autocomplete($counter, " . Json::encode($data) . ");");
+
+ return $this;
+ }
}
diff --git a/extensions/gii/components/DiffRendererHtmlInline.php b/extensions/gii/components/DiffRendererHtmlInline.php
index 908630af95f..dbaf007bce4 100644
--- a/extensions/gii/components/DiffRendererHtmlInline.php
+++ b/extensions/gii/components/DiffRendererHtmlInline.php
@@ -15,121 +15,122 @@
*/
class DiffRendererHtmlInline extends \Diff_Renderer_Html_Array
{
- /**
- * Render a and return diff with changes between the two sequences
- * displayed inline (under each other)
- *
- * @return string The generated inline diff.
- */
- public function render()
- {
- $changes = parent::render();
- $html = '';
- if (empty($changes)) {
- return $html;
- }
+ /**
+ * Render a and return diff with changes between the two sequences
+ * displayed inline (under each other)
+ *
+ * @return string The generated inline diff.
+ */
+ public function render()
+ {
+ $changes = parent::render();
+ $html = '';
+ if (empty($changes)) {
+ return $html;
+ }
- $html .= <<
-
-
-
Old
-
New
-
Differences
-
-
+
+
+
Old
+
New
+
Differences
+
+
HTML;
- foreach ($changes as $i => $blocks) {
- // If this is a separate block, we're condensing code so output ...,
- // indicating a significant portion of the code has been collapsed as
- // it is the same
- if ($i > 0) {
- $html .= <<
-
-
-
-
+ foreach ($changes as $i => $blocks) {
+ // If this is a separate block, we're condensing code so output ...,
+ // indicating a significant portion of the code has been collapsed as
+ // it is the same
+ if ($i > 0) {
+ $html .= <<
+
+
+
+
HTML;
- }
+ }
- foreach ($blocks as $change) {
- $tag = ucfirst($change['tag']);
- $html .= <<
+ foreach ($blocks as $change) {
+ $tag = ucfirst($change['tag']);
+ $html .= <<
HTML;
- // Equal changes should be shown on both sides of the diff
- if ($change['tag'] === 'equal') {
- foreach ($change['base']['lines'] as $no => $line) {
- $fromLine = $change['base']['offset'] + $no + 1;
- $toLine = $change['changed']['offset'] + $no + 1;
- $html .= <<
-
-
-
{$line}
-
+ // Equal changes should be shown on both sides of the diff
+ if ($change['tag'] === 'equal') {
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= <<
+
+
+
{$line}
+
HTML;
- }
- }
- // Added lines only on the right side
- elseif ($change['tag'] === 'insert') {
- foreach ($change['changed']['lines'] as $no => $line) {
- $toLine = $change['changed']['offset'] + $no + 1;
- $html .= <<
-
-
-
{$line}
-
+ }
+ }
+ // Added lines only on the right side
+ elseif ($change['tag'] === 'insert') {
+ foreach ($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= <<
+
+
+
{$line}
+
HTML;
- }
- }
- // Show deleted lines only on the left side
- elseif ($change['tag'] === 'delete') {
- foreach ($change['base']['lines'] as $no => $line) {
- $fromLine = $change['base']['offset'] + $no + 1;
- $html .= <<
-
-
-
{$line}
-
+ }
+ }
+ // Show deleted lines only on the left side
+ elseif ($change['tag'] === 'delete') {
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= <<
+
';
- }
- }
- }
- }
- throw new NotFoundHttpException("Code file not found: $file");
- }
+ return $this->render('view', $params);
+ }
- public function actionDiff($id, $file)
- {
- $generator = $this->loadGenerator($id);
- if ($generator->validate()) {
- foreach ($generator->generate() as $f) {
- if ($f->id === $file) {
- return $this->renderPartial('diff', [
- 'diff' => $f->diff(),
- ]);
- }
- }
- }
- throw new NotFoundHttpException("Code file not found: $file");
- }
+ public function actionPreview($id, $file)
+ {
+ $generator = $this->loadGenerator($id);
+ if ($generator->validate()) {
+ foreach ($generator->generate() as $f) {
+ if ($f->id === $file) {
+ $content = $f->preview();
+ if ($content !== false) {
+ return '
' . $content . '';
+ } else {
+ return '
Preview is not available for this file type.
';
+ }
+ }
+ }
+ }
+ throw new NotFoundHttpException("Code file not found: $file");
+ }
- /**
- * Runs an action defined in the generator.
- * Given an action named "xyz", the method "actionXyz()" in the generator will be called.
- * If the method does not exist, a 400 HTTP exception will be thrown.
- * @param string $id the ID of the generator
- * @param string $name the action name
- * @return mixed the result of the action.
- * @throws NotFoundHttpException if the action method does not exist.
- */
- public function actionAction($id, $name)
- {
- $generator = $this->loadGenerator($id);
- $method = 'action' . $name;
- if (method_exists($generator, $method)) {
- return $generator->$method();
- } else {
- throw new NotFoundHttpException("Unknown generator action: $name");
- }
- }
+ public function actionDiff($id, $file)
+ {
+ $generator = $this->loadGenerator($id);
+ if ($generator->validate()) {
+ foreach ($generator->generate() as $f) {
+ if ($f->id === $file) {
+ return $this->renderPartial('diff', [
+ 'diff' => $f->diff(),
+ ]);
+ }
+ }
+ }
+ throw new NotFoundHttpException("Code file not found: $file");
+ }
- /**
- * Loads the generator with the specified ID.
- * @param string $id the ID of the generator to be loaded.
- * @return \yii\gii\Generator the loaded generator
- * @throws NotFoundHttpException
- */
- protected function loadGenerator($id)
- {
- if (isset($this->module->generators[$id])) {
- $this->generator = $this->module->generators[$id];
- $this->generator->loadStickyAttributes();
- $this->generator->load($_POST);
- return $this->generator;
- } else {
- throw new NotFoundHttpException("Code generator not found: $id");
- }
- }
+ /**
+ * Runs an action defined in the generator.
+ * Given an action named "xyz", the method "actionXyz()" in the generator will be called.
+ * If the method does not exist, a 400 HTTP exception will be thrown.
+ * @param string $id the ID of the generator
+ * @param string $name the action name
+ * @return mixed the result of the action.
+ * @throws NotFoundHttpException if the action method does not exist.
+ */
+ public function actionAction($id, $name)
+ {
+ $generator = $this->loadGenerator($id);
+ $method = 'action' . $name;
+ if (method_exists($generator, $method)) {
+ return $generator->$method();
+ } else {
+ throw new NotFoundHttpException("Unknown generator action: $name");
+ }
+ }
+
+ /**
+ * Loads the generator with the specified ID.
+ * @param string $id the ID of the generator to be loaded.
+ * @return \yii\gii\Generator the loaded generator
+ * @throws NotFoundHttpException
+ */
+ protected function loadGenerator($id)
+ {
+ if (isset($this->module->generators[$id])) {
+ $this->generator = $this->module->generators[$id];
+ $this->generator->loadStickyAttributes();
+ $this->generator->load($_POST);
+
+ return $this->generator;
+ } else {
+ throw new NotFoundHttpException("Code generator not found: $id");
+ }
+ }
}
diff --git a/extensions/gii/generators/controller/Generator.php b/extensions/gii/generators/controller/Generator.php
index 0e2d8ff79dd..7393397ec12 100644
--- a/extensions/gii/generators/controller/Generator.php
+++ b/extensions/gii/generators/controller/Generator.php
@@ -29,217 +29,222 @@
*/
class Generator extends \yii\gii\Generator
{
- /**
- * @var string the controller ID
- */
- public $controller;
- /**
- * @var string the base class of the controller
- */
- public $baseClass = 'yii\web\Controller';
- /**
- * @var string the namespace of the controller class
- */
- public $ns;
- /**
- * @var string list of action IDs separated by commas or spaces
- */
- public $actions = 'index';
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- $this->ns = \Yii::$app->controllerNamespace;
- }
-
- /**
- * @inheritdoc
- */
- public function getName()
- {
- return 'Controller Generator';
- }
-
- /**
- * @inheritdoc
- */
- public function getDescription()
- {
- return 'This generator helps you to quickly generate a new controller class,
- one or several controller actions and their corresponding views.';
- }
-
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return array_merge(parent::rules(), [
- [['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'],
- [['controller', 'baseClass'], 'required'],
- [['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.'],
- ]);
- }
-
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'baseClass' => 'Base Class',
- 'controller' => 'Controller ID',
- 'actions' => 'Action IDs',
- 'ns' => 'Controller Namespace',
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function requiredTemplates()
- {
- return [
- 'controller.php',
- 'view.php',
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function stickyAttributes()
- {
- return ['ns', 'baseClass'];
- }
-
- /**
- * @inheritdoc
- */
- public function hints()
- {
- return [
- 'controller' => 'Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example:
-
-
order generates OrderController.php
-
order-item generates OrderItemController.php
-
admin/user generates UserController.php within the admin module.
-
',
- 'actions' => 'Provide one or multiple action IDs to generate empty action method(s) in the controller. Separate multiple action IDs with commas or spaces.
- Action IDs should be in lower case. For example:
-
-
index generates actionIndex()
-
create-order generates actionCreateOrder()
-
',
- 'ns' => 'This is the namespace that the new controller class will use.',
- 'baseClass' => 'This is the class that the new controller class will extend from. Please make sure the class exists and can be autoloaded.',
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function successMessage()
- {
- $actions = $this->getActionIDs();
- if (in_array('index', $actions)) {
- $route = $this->controller . '/index';
- } else {
- $route = $this->controller . '/' . reset($actions);
- }
- $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($route), ['target' => '_blank']);
- return "The controller has been generated successfully. You may $link.";
- }
-
- /**
- * @inheritdoc
- */
- public function generate()
- {
- $files = [];
-
- $files[] = new CodeFile(
- $this->getControllerFile(),
- $this->render('controller.php')
- );
-
- foreach ($this->getActionIDs() as $action) {
- $files[] = new CodeFile(
- $this->getViewFile($action),
- $this->render('view.php', ['action' => $action])
- );
- }
-
- return $files;
- }
-
- /**
- * Normalizes [[actions]] into an array of action IDs.
- * @return array an array of action IDs entered by the user
- */
- public function getActionIDs()
- {
- $actions = array_unique(preg_split('/[\s,]+/', $this->actions, -1, PREG_SPLIT_NO_EMPTY));
- sort($actions);
- return $actions;
- }
-
- /**
- * @return string the controller class name without the namespace part.
- */
- public function getControllerClass()
- {
- return Inflector::id2camel($this->getControllerID()) . 'Controller';
- }
-
- /**
- * @return string the controller ID (without the module ID prefix)
- */
- public function getControllerID()
- {
- if (($pos = strrpos($this->controller, '/')) !== false) {
- return substr($this->controller, $pos + 1);
- } else {
- return $this->controller;
- }
- }
-
- /**
- * @return \yii\base\Module the module that the new controller belongs to
- */
- public function getModule()
- {
- if (($pos = strrpos($this->controller, '/')) !== false) {
- $id = substr($this->controller, 0, $pos);
- if (($module = Yii::$app->getModule($id)) !== null) {
- return $module;
- }
- }
- return Yii::$app;
- }
-
- /**
- * @return string the controller class file path
- */
- public function getControllerFile()
- {
- $module = $this->getModule();
- return $module->getControllerPath() . '/' . $this->getControllerClass() . '.php';
- }
-
- /**
- * @param string $action the action ID
- * @return string the action view file path
- */
- public function getViewFile($action)
- {
- $module = $this->getModule();
- return $module->getViewPath() . '/' . $this->getControllerID() . '/' . $action . '.php';
- }
+ /**
+ * @var string the controller ID
+ */
+ public $controller;
+ /**
+ * @var string the base class of the controller
+ */
+ public $baseClass = 'yii\web\Controller';
+ /**
+ * @var string the namespace of the controller class
+ */
+ public $ns;
+ /**
+ * @var string list of action IDs separated by commas or spaces
+ */
+ public $actions = 'index';
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ $this->ns = \Yii::$app->controllerNamespace;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return 'Controller Generator';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getDescription()
+ {
+ return 'This generator helps you to quickly generate a new controller class,
+ one or several controller actions and their corresponding views.';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return array_merge(parent::rules(), [
+ [['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'],
+ [['controller', 'baseClass'], 'required'],
+ [['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.'],
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'baseClass' => 'Base Class',
+ 'controller' => 'Controller ID',
+ 'actions' => 'Action IDs',
+ 'ns' => 'Controller Namespace',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function requiredTemplates()
+ {
+ return [
+ 'controller.php',
+ 'view.php',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function stickyAttributes()
+ {
+ return ['ns', 'baseClass'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function hints()
+ {
+ return [
+ 'controller' => 'Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example:
+
+
order generates OrderController.php
+
order-item generates OrderItemController.php
+
admin/user generates UserController.php within the admin module.
+
',
+ 'actions' => 'Provide one or multiple action IDs to generate empty action method(s) in the controller. Separate multiple action IDs with commas or spaces.
+ Action IDs should be in lower case. For example:
+
+
index generates actionIndex()
+
create-order generates actionCreateOrder()
+
',
+ 'ns' => 'This is the namespace that the new controller class will use.',
+ 'baseClass' => 'This is the class that the new controller class will extend from. Please make sure the class exists and can be autoloaded.',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function successMessage()
+ {
+ $actions = $this->getActionIDs();
+ if (in_array('index', $actions)) {
+ $route = $this->controller . '/index';
+ } else {
+ $route = $this->controller . '/' . reset($actions);
+ }
+ $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($route), ['target' => '_blank']);
+
+ return "The controller has been generated successfully. You may $link.";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function generate()
+ {
+ $files = [];
+
+ $files[] = new CodeFile(
+ $this->getControllerFile(),
+ $this->render('controller.php')
+ );
+
+ foreach ($this->getActionIDs() as $action) {
+ $files[] = new CodeFile(
+ $this->getViewFile($action),
+ $this->render('view.php', ['action' => $action])
+ );
+ }
+
+ return $files;
+ }
+
+ /**
+ * Normalizes [[actions]] into an array of action IDs.
+ * @return array an array of action IDs entered by the user
+ */
+ public function getActionIDs()
+ {
+ $actions = array_unique(preg_split('/[\s,]+/', $this->actions, -1, PREG_SPLIT_NO_EMPTY));
+ sort($actions);
+
+ return $actions;
+ }
+
+ /**
+ * @return string the controller class name without the namespace part.
+ */
+ public function getControllerClass()
+ {
+ return Inflector::id2camel($this->getControllerID()) . 'Controller';
+ }
+
+ /**
+ * @return string the controller ID (without the module ID prefix)
+ */
+ public function getControllerID()
+ {
+ if (($pos = strrpos($this->controller, '/')) !== false) {
+ return substr($this->controller, $pos + 1);
+ } else {
+ return $this->controller;
+ }
+ }
+
+ /**
+ * @return \yii\base\Module the module that the new controller belongs to
+ */
+ public function getModule()
+ {
+ if (($pos = strrpos($this->controller, '/')) !== false) {
+ $id = substr($this->controller, 0, $pos);
+ if (($module = Yii::$app->getModule($id)) !== null) {
+ return $module;
+ }
+ }
+
+ return Yii::$app;
+ }
+
+ /**
+ * @return string the controller class file path
+ */
+ public function getControllerFile()
+ {
+ $module = $this->getModule();
+
+ return $module->getControllerPath() . '/' . $this->getControllerClass() . '.php';
+ }
+
+ /**
+ * @param string $action the action ID
+ * @return string the action view file path
+ */
+ public function getViewFile($action)
+ {
+ $module = $this->getModule();
+
+ return $module->getViewPath() . '/' . $this->getControllerID() . '/' . $action . '.php';
+ }
}
diff --git a/extensions/gii/generators/controller/templates/controller.php b/extensions/gii/generators/controller/templates/controller.php
index 95358d21405..8447809314f 100644
--- a/extensions/gii/generators/controller/templates/controller.php
+++ b/extensions/gii/generators/controller/templates/controller.php
@@ -19,10 +19,10 @@
class = $generator->getControllerClass() ?> extends = '\\' . trim($generator->baseClass, '\\') . "\n" ?>
{
getActionIDs() as $action): ?>
- public function action= Inflector::id2camel($action) ?>()
- {
- return $this->render('= $action ?>');
- }
+ public function action= Inflector::id2camel($action) ?>()
+ {
+ return $this->render('= $action ?>');
+ }
}
diff --git a/extensions/gii/generators/controller/templates/view.php b/extensions/gii/generators/controller/templates/view.php
index a458b16ef77..611cb1be75a 100644
--- a/extensions/gii/generators/controller/templates/view.php
+++ b/extensions/gii/generators/controller/templates/view.php
@@ -17,6 +17,6 @@
- You may change the content of this page by modifying
- the file = '=' ?> __FILE__; ?>.
+ You may change the content of this page by modifying
+ the file = '=' ?> __FILE__; ?>.
diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php
index 116f8395f41..b1ca62d167e 100644
--- a/extensions/gii/generators/crud/Generator.php
+++ b/extensions/gii/generators/crud/Generator.php
@@ -30,473 +30,480 @@
*/
class Generator extends \yii\gii\Generator
{
- public $modelClass;
- public $moduleID;
- public $controllerClass;
- public $baseControllerClass = 'yii\web\Controller';
- public $indexWidgetType = 'grid';
- public $searchModelClass;
-
- /**
- * @inheritdoc
- */
- public function getName()
- {
- return 'CRUD Generator';
- }
-
- /**
- * @inheritdoc
- */
- public function getDescription()
- {
- return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete)
- operations for the specified data model.';
- }
-
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return array_merge(parent::rules(), [
- [['moduleID', 'controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'],
- [['modelClass', 'searchModelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'],
- [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'],
- [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
- [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]],
- [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]],
- [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'],
- [['controllerClass', 'searchModelClass'], 'validateNewClass'],
- [['indexWidgetType'], 'in', 'range' => ['grid', 'list']],
- [['modelClass'], 'validateModelClass'],
- [['moduleID'], 'validateModuleID'],
- ]);
- }
-
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return array_merge(parent::attributeLabels(), [
- 'modelClass' => 'Model Class',
- 'moduleID' => 'Module ID',
- 'controllerClass' => 'Controller Class',
- 'baseControllerClass' => 'Base Controller Class',
- 'indexWidgetType' => 'Widget Used in Index Page',
- 'searchModelClass' => 'Search Model Class',
- ]);
- }
-
- /**
- * @inheritdoc
- */
- public function hints()
- {
- return [
- 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon.
- You should provide a fully qualified class name, e.g., app\models\Post.',
- 'controllerClass' => 'This is the name of the controller class to be generated. You should
- provide a fully qualified namespaced class, .e.g, app\controllers\PostController.',
- 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from.
- You should provide a fully qualified class name, e.g., yii\web\Controller.',
- 'moduleID' => 'This is the ID of the module that the generated controller will belong to.
- If not set, it means the controller will belong to the application.',
- 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models.
- You may choose either GridView or ListView',
- 'searchModelClass' => 'This is the name of the search model class to be generated. You should provide a fully
- qualified namespaced class name, e.g., app\models\search\PostSearch.',
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function requiredTemplates()
- {
- return ['controller.php'];
- }
-
- /**
- * @inheritdoc
- */
- public function stickyAttributes()
- {
- return ['baseControllerClass', 'moduleID', 'indexWidgetType'];
- }
-
- /**
- * Checks if model class is valid
- */
- public function validateModelClass()
- {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $pk = $class::primaryKey();
- if (empty($pk)) {
- $this->addError('modelClass', "The table associated with $class must have primary key(s).");
- }
- }
-
- /**
- * Checks if model ID is valid
- */
- public function validateModuleID()
- {
- if (!empty($this->moduleID)) {
- $module = Yii::$app->getModule($this->moduleID);
- if ($module === null) {
- $this->addError('moduleID', "Module '{$this->moduleID}' does not exist.");
- }
- }
- }
-
- /**
- * @inheritdoc
- */
- public function generate()
- {
- $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php');
- $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php'));
- $files = [
- new CodeFile($controllerFile, $this->render('controller.php')),
- new CodeFile($searchModel, $this->render('search.php')),
- ];
-
- $viewPath = $this->getViewPath();
- $templatePath = $this->getTemplatePath() . '/views';
- foreach (scandir($templatePath) as $file) {
- if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') {
- $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file"));
- }
- }
-
-
- return $files;
- }
-
- /**
- * @return string the controller ID (without the module ID prefix)
- */
- public function getControllerID()
- {
- $pos = strrpos($this->controllerClass, '\\');
- $class = substr(substr($this->controllerClass, $pos + 1), 0, -10);
- return Inflector::camel2id($class);
- }
-
- /**
- * @return string the action view file path
- */
- public function getViewPath()
- {
- $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID);
- return $module->getViewPath() . '/' . $this->getControllerID() ;
- }
-
- public function getNameAttribute()
- {
- foreach ($this->getColumnNames() as $name) {
- if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) {
- return $name;
- }
- }
- /** @var \yii\db\ActiveRecord $class */
- $class = $this->modelClass;
- $pk = $class::primaryKey();
- return $pk[0];
- }
-
- /**
- * Generates code for active field
- * @param string $attribute
- * @return string
- */
- public function generateActiveField($attribute)
- {
- $tableSchema = $this->getTableSchema();
- if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) {
- if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) {
- return "\$form->field(\$model, '$attribute')->passwordInput()";
- } else {
- return "\$form->field(\$model, '$attribute')";
- }
- }
- $column = $tableSchema->columns[$attribute];
- if ($column->phpType === 'boolean') {
- return "\$form->field(\$model, '$attribute')->checkbox()";
- } elseif ($column->type === 'text') {
- return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])";
- } else {
- if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) {
- $input = 'passwordInput';
- } else {
- $input = 'textInput';
- }
- if ($column->phpType !== 'string' || $column->size === null) {
- return "\$form->field(\$model, '$attribute')->$input()";
- } else {
- return "\$form->field(\$model, '$attribute')->$input(['maxlength' => $column->size])";
- }
- }
- }
-
- /**
- * Generates code for active search field
- * @param string $attribute
- * @return string
- */
- public function generateActiveSearchField($attribute)
- {
- $tableSchema = $this->getTableSchema();
- if ($tableSchema === false) {
- return "\$form->field(\$model, '$attribute')";
- }
- $column = $tableSchema->columns[$attribute];
- if ($column->phpType === 'boolean') {
- return "\$form->field(\$model, '$attribute')->checkbox()";
- } else {
- return "\$form->field(\$model, '$attribute')";
- }
- }
-
- /**
- * Generates column format
- * @param \yii\db\ColumnSchema $column
- * @return string
- */
- public function generateColumnFormat($column)
- {
- if ($column->phpType === 'boolean') {
- return 'boolean';
- } elseif ($column->type === 'text') {
- return 'ntext';
- } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') {
- return 'datetime';
- } elseif (stripos($column->name, 'email') !== false) {
- return 'email';
- } elseif (stripos($column->name, 'url') !== false) {
- return 'url';
- } else {
- return 'text';
- }
- }
-
- /**
- * Generates validation rules for the search model.
- * @return array the generated validation rules
- */
- public function generateSearchRules()
- {
- if (($table = $this->getTableSchema()) === false) {
- return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"];
- }
- $types = [];
- foreach ($table->columns as $column) {
- switch ($column->type) {
- case Schema::TYPE_SMALLINT:
- case Schema::TYPE_INTEGER:
- case Schema::TYPE_BIGINT:
- $types['integer'][] = $column->name;
- break;
- case Schema::TYPE_BOOLEAN:
- $types['boolean'][] = $column->name;
- break;
- case Schema::TYPE_FLOAT:
- case Schema::TYPE_DECIMAL:
- case Schema::TYPE_MONEY:
- $types['number'][] = $column->name;
- break;
- case Schema::TYPE_DATE:
- case Schema::TYPE_TIME:
- case Schema::TYPE_DATETIME:
- case Schema::TYPE_TIMESTAMP:
- default:
- $types['safe'][] = $column->name;
- break;
- }
- }
-
- $rules = [];
- foreach ($types as $type => $columns) {
- $rules[] = "[['" . implode("', '", $columns) . "'], '$type']";
- }
-
- return $rules;
- }
-
- /**
- * @return array searchable attributes
- */
- public function getSearchAttributes()
- {
- return $this->getColumnNames();
- }
-
- /**
- * Generates the attribute labels for the search model.
- * @return array the generated attribute labels (name => label)
- */
- public function generateSearchLabels()
- {
- /** @var \yii\base\Model $model */
- $model = new $this->modelClass();
- $attributeLabels = $model->attributeLabels();
- $labels = [];
- foreach ($this->getColumnNames() as $name) {
- if (isset($attributeLabels[$name])) {
- $labels[$name] = $attributeLabels[$name];
- } else {
- if (!strcasecmp($name, 'id')) {
- $labels[$name] = 'ID';
- } else {
- $label = Inflector::camel2words($name);
- if (strcasecmp(substr($label, -3), ' id') === 0) {
- $label = substr($label, 0, -3) . ' ID';
- }
- $labels[$name] = $label;
- }
- }
- }
- return $labels;
- }
-
- /**
- * Generates search conditions
- * @return array
- */
- public function generateSearchConditions()
- {
- $columns = [];
- if (($table = $this->getTableSchema()) === false) {
- $class = $this->modelClass;
- /** @var \yii\base\Model $model */
- $model = new $class();
- foreach ($model->attributes() as $attribute) {
- $columns[$attribute] = 'unknown';
- }
- } else {
- foreach ($table->columns as $column) {
- $columns[$column->name] = $column->type;
- }
- }
- $conditions = [];
- foreach ($columns as $column => $type) {
- switch ($type) {
- case Schema::TYPE_SMALLINT:
- case Schema::TYPE_INTEGER:
- case Schema::TYPE_BIGINT:
- case Schema::TYPE_BOOLEAN:
- case Schema::TYPE_FLOAT:
- case Schema::TYPE_DECIMAL:
- case Schema::TYPE_MONEY:
- case Schema::TYPE_DATE:
- case Schema::TYPE_TIME:
- case Schema::TYPE_DATETIME:
- case Schema::TYPE_TIMESTAMP:
- $conditions[] = "\$this->addCondition(\$query, '{$column}');";
- break;
- default:
- $conditions[] = "\$this->addCondition(\$query, '{$column}', true);";
- break;
- }
- }
-
- return $conditions;
- }
-
- /**
- * Generates URL parameters
- * @return string
- */
- public function generateUrlParams()
- {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $pks = $class::primaryKey();
- if (count($pks) === 1) {
- return "'id' => \$model->{$pks[0]}";
- } else {
- $params = [];
- foreach ($pks as $pk) {
- $params[] = "'$pk' => \$model->$pk";
- }
- return implode(', ', $params);
- }
- }
-
- /**
- * Generates action parameters
- * @return string
- */
- public function generateActionParams()
- {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $pks = $class::primaryKey();
- if (count($pks) === 1) {
- return '$id';
- } else {
- return '$' . implode(', $', $pks);
- }
- }
-
- /**
- * Generates parameter tags for phpdoc
- * @return array parameter tags for phpdoc
- */
- public function generateActionParamComments()
- {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $pks = $class::primaryKey();
- if (($table = $this->getTableSchema()) === false) {
- $params = [];
- foreach ($pks as $pk) {
- $params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk;
- }
- return $params;
- }
- if (count($pks) === 1) {
- return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id'];
- } else {
- $params = [];
- foreach ($pks as $pk) {
- $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk;
- }
- return $params;
- }
- }
-
- /**
- * Returns table schema for current model class or false if it is not an active record
- * @return boolean|\yii\db\TableSchema
- */
- public function getTableSchema()
- {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
- return $class::getTableSchema();
- } else {
- return false;
- }
- }
-
- /**
- * @return array model column names
- */
- public function getColumnNames()
- {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
- return $class::getTableSchema()->getColumnNames();
- } else {
- /** @var \yii\base\Model $model */
- $model = new $class();
- return $model->attributes();
- }
- }
+ public $modelClass;
+ public $moduleID;
+ public $controllerClass;
+ public $baseControllerClass = 'yii\web\Controller';
+ public $indexWidgetType = 'grid';
+ public $searchModelClass;
+
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return 'CRUD Generator';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getDescription()
+ {
+ return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete)
+ operations for the specified data model.';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return array_merge(parent::rules(), [
+ [['moduleID', 'controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'],
+ [['modelClass', 'searchModelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'],
+ [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'],
+ [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
+ [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]],
+ [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]],
+ [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'],
+ [['controllerClass', 'searchModelClass'], 'validateNewClass'],
+ [['indexWidgetType'], 'in', 'range' => ['grid', 'list']],
+ [['modelClass'], 'validateModelClass'],
+ [['moduleID'], 'validateModuleID'],
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return array_merge(parent::attributeLabels(), [
+ 'modelClass' => 'Model Class',
+ 'moduleID' => 'Module ID',
+ 'controllerClass' => 'Controller Class',
+ 'baseControllerClass' => 'Base Controller Class',
+ 'indexWidgetType' => 'Widget Used in Index Page',
+ 'searchModelClass' => 'Search Model Class',
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function hints()
+ {
+ return [
+ 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon.
+ You should provide a fully qualified class name, e.g., app\models\Post.',
+ 'controllerClass' => 'This is the name of the controller class to be generated. You should
+ provide a fully qualified namespaced class, .e.g, app\controllers\PostController.',
+ 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from.
+ You should provide a fully qualified class name, e.g., yii\web\Controller.',
+ 'moduleID' => 'This is the ID of the module that the generated controller will belong to.
+ If not set, it means the controller will belong to the application.',
+ 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models.
+ You may choose either GridView or ListView',
+ 'searchModelClass' => 'This is the name of the search model class to be generated. You should provide a fully
+ qualified namespaced class name, e.g., app\models\search\PostSearch.',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function requiredTemplates()
+ {
+ return ['controller.php'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function stickyAttributes()
+ {
+ return ['baseControllerClass', 'moduleID', 'indexWidgetType'];
+ }
+
+ /**
+ * Checks if model class is valid
+ */
+ public function validateModelClass()
+ {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $pk = $class::primaryKey();
+ if (empty($pk)) {
+ $this->addError('modelClass', "The table associated with $class must have primary key(s).");
+ }
+ }
+
+ /**
+ * Checks if model ID is valid
+ */
+ public function validateModuleID()
+ {
+ if (!empty($this->moduleID)) {
+ $module = Yii::$app->getModule($this->moduleID);
+ if ($module === null) {
+ $this->addError('moduleID', "Module '{$this->moduleID}' does not exist.");
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function generate()
+ {
+ $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php');
+ $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php'));
+ $files = [
+ new CodeFile($controllerFile, $this->render('controller.php')),
+ new CodeFile($searchModel, $this->render('search.php')),
+ ];
+
+ $viewPath = $this->getViewPath();
+ $templatePath = $this->getTemplatePath() . '/views';
+ foreach (scandir($templatePath) as $file) {
+ if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') {
+ $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file"));
+ }
+ }
+
+ return $files;
+ }
+
+ /**
+ * @return string the controller ID (without the module ID prefix)
+ */
+ public function getControllerID()
+ {
+ $pos = strrpos($this->controllerClass, '\\');
+ $class = substr(substr($this->controllerClass, $pos + 1), 0, -10);
+
+ return Inflector::camel2id($class);
+ }
+
+ /**
+ * @return string the action view file path
+ */
+ public function getViewPath()
+ {
+ $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID);
+
+ return $module->getViewPath() . '/' . $this->getControllerID() ;
+ }
+
+ public function getNameAttribute()
+ {
+ foreach ($this->getColumnNames() as $name) {
+ if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) {
+ return $name;
+ }
+ }
+ /** @var \yii\db\ActiveRecord $class */
+ $class = $this->modelClass;
+ $pk = $class::primaryKey();
+
+ return $pk[0];
+ }
+
+ /**
+ * Generates code for active field
+ * @param string $attribute
+ * @return string
+ */
+ public function generateActiveField($attribute)
+ {
+ $tableSchema = $this->getTableSchema();
+ if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) {
+ if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) {
+ return "\$form->field(\$model, '$attribute')->passwordInput()";
+ } else {
+ return "\$form->field(\$model, '$attribute')";
+ }
+ }
+ $column = $tableSchema->columns[$attribute];
+ if ($column->phpType === 'boolean') {
+ return "\$form->field(\$model, '$attribute')->checkbox()";
+ } elseif ($column->type === 'text') {
+ return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])";
+ } else {
+ if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) {
+ $input = 'passwordInput';
+ } else {
+ $input = 'textInput';
+ }
+ if ($column->phpType !== 'string' || $column->size === null) {
+ return "\$form->field(\$model, '$attribute')->$input()";
+ } else {
+ return "\$form->field(\$model, '$attribute')->$input(['maxlength' => $column->size])";
+ }
+ }
+ }
+
+ /**
+ * Generates code for active search field
+ * @param string $attribute
+ * @return string
+ */
+ public function generateActiveSearchField($attribute)
+ {
+ $tableSchema = $this->getTableSchema();
+ if ($tableSchema === false) {
+ return "\$form->field(\$model, '$attribute')";
+ }
+ $column = $tableSchema->columns[$attribute];
+ if ($column->phpType === 'boolean') {
+ return "\$form->field(\$model, '$attribute')->checkbox()";
+ } else {
+ return "\$form->field(\$model, '$attribute')";
+ }
+ }
+
+ /**
+ * Generates column format
+ * @param \yii\db\ColumnSchema $column
+ * @return string
+ */
+ public function generateColumnFormat($column)
+ {
+ if ($column->phpType === 'boolean') {
+ return 'boolean';
+ } elseif ($column->type === 'text') {
+ return 'ntext';
+ } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') {
+ return 'datetime';
+ } elseif (stripos($column->name, 'email') !== false) {
+ return 'email';
+ } elseif (stripos($column->name, 'url') !== false) {
+ return 'url';
+ } else {
+ return 'text';
+ }
+ }
+
+ /**
+ * Generates validation rules for the search model.
+ * @return array the generated validation rules
+ */
+ public function generateSearchRules()
+ {
+ if (($table = $this->getTableSchema()) === false) {
+ return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"];
+ }
+ $types = [];
+ foreach ($table->columns as $column) {
+ switch ($column->type) {
+ case Schema::TYPE_SMALLINT:
+ case Schema::TYPE_INTEGER:
+ case Schema::TYPE_BIGINT:
+ $types['integer'][] = $column->name;
+ break;
+ case Schema::TYPE_BOOLEAN:
+ $types['boolean'][] = $column->name;
+ break;
+ case Schema::TYPE_FLOAT:
+ case Schema::TYPE_DECIMAL:
+ case Schema::TYPE_MONEY:
+ $types['number'][] = $column->name;
+ break;
+ case Schema::TYPE_DATE:
+ case Schema::TYPE_TIME:
+ case Schema::TYPE_DATETIME:
+ case Schema::TYPE_TIMESTAMP:
+ default:
+ $types['safe'][] = $column->name;
+ break;
+ }
+ }
+
+ $rules = [];
+ foreach ($types as $type => $columns) {
+ $rules[] = "[['" . implode("', '", $columns) . "'], '$type']";
+ }
+
+ return $rules;
+ }
+
+ /**
+ * @return array searchable attributes
+ */
+ public function getSearchAttributes()
+ {
+ return $this->getColumnNames();
+ }
+
+ /**
+ * Generates the attribute labels for the search model.
+ * @return array the generated attribute labels (name => label)
+ */
+ public function generateSearchLabels()
+ {
+ /** @var \yii\base\Model $model */
+ $model = new $this->modelClass();
+ $attributeLabels = $model->attributeLabels();
+ $labels = [];
+ foreach ($this->getColumnNames() as $name) {
+ if (isset($attributeLabels[$name])) {
+ $labels[$name] = $attributeLabels[$name];
+ } else {
+ if (!strcasecmp($name, 'id')) {
+ $labels[$name] = 'ID';
+ } else {
+ $label = Inflector::camel2words($name);
+ if (strcasecmp(substr($label, -3), ' id') === 0) {
+ $label = substr($label, 0, -3) . ' ID';
+ }
+ $labels[$name] = $label;
+ }
+ }
+ }
+
+ return $labels;
+ }
+
+ /**
+ * Generates search conditions
+ * @return array
+ */
+ public function generateSearchConditions()
+ {
+ $columns = [];
+ if (($table = $this->getTableSchema()) === false) {
+ $class = $this->modelClass;
+ /** @var \yii\base\Model $model */
+ $model = new $class();
+ foreach ($model->attributes() as $attribute) {
+ $columns[$attribute] = 'unknown';
+ }
+ } else {
+ foreach ($table->columns as $column) {
+ $columns[$column->name] = $column->type;
+ }
+ }
+ $conditions = [];
+ foreach ($columns as $column => $type) {
+ switch ($type) {
+ case Schema::TYPE_SMALLINT:
+ case Schema::TYPE_INTEGER:
+ case Schema::TYPE_BIGINT:
+ case Schema::TYPE_BOOLEAN:
+ case Schema::TYPE_FLOAT:
+ case Schema::TYPE_DECIMAL:
+ case Schema::TYPE_MONEY:
+ case Schema::TYPE_DATE:
+ case Schema::TYPE_TIME:
+ case Schema::TYPE_DATETIME:
+ case Schema::TYPE_TIMESTAMP:
+ $conditions[] = "\$this->addCondition(\$query, '{$column}');";
+ break;
+ default:
+ $conditions[] = "\$this->addCondition(\$query, '{$column}', true);";
+ break;
+ }
+ }
+
+ return $conditions;
+ }
+
+ /**
+ * Generates URL parameters
+ * @return string
+ */
+ public function generateUrlParams()
+ {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $pks = $class::primaryKey();
+ if (count($pks) === 1) {
+ return "'id' => \$model->{$pks[0]}";
+ } else {
+ $params = [];
+ foreach ($pks as $pk) {
+ $params[] = "'$pk' => \$model->$pk";
+ }
+
+ return implode(', ', $params);
+ }
+ }
+
+ /**
+ * Generates action parameters
+ * @return string
+ */
+ public function generateActionParams()
+ {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $pks = $class::primaryKey();
+ if (count($pks) === 1) {
+ return '$id';
+ } else {
+ return '$' . implode(', $', $pks);
+ }
+ }
+
+ /**
+ * Generates parameter tags for phpdoc
+ * @return array parameter tags for phpdoc
+ */
+ public function generateActionParamComments()
+ {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $pks = $class::primaryKey();
+ if (($table = $this->getTableSchema()) === false) {
+ $params = [];
+ foreach ($pks as $pk) {
+ $params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk;
+ }
+
+ return $params;
+ }
+ if (count($pks) === 1) {
+ return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id'];
+ } else {
+ $params = [];
+ foreach ($pks as $pk) {
+ $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk;
+ }
+
+ return $params;
+ }
+ }
+
+ /**
+ * Returns table schema for current model class or false if it is not an active record
+ * @return boolean|\yii\db\TableSchema
+ */
+ public function getTableSchema()
+ {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
+ return $class::getTableSchema();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @return array model column names
+ */
+ public function getColumnNames()
+ {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
+ return $class::getTableSchema()->getColumnNames();
+ } else {
+ /** @var \yii\base\Model $model */
+ $model = new $class();
+
+ return $model->attributes();
+ }
+ }
}
diff --git a/extensions/gii/generators/crud/form.php b/extensions/gii/generators/crud/form.php
index 1b101c24896..36f4b283da0 100644
--- a/extensions/gii/generators/crud/form.php
+++ b/extensions/gii/generators/crud/form.php
@@ -11,6 +11,6 @@
echo $form->field($generator, 'baseControllerClass');
echo $form->field($generator, 'moduleID');
echo $form->field($generator, 'indexWidgetType')->dropDownList([
- 'grid' => 'GridView',
- 'list' => 'ListView',
+ 'grid' => 'GridView',
+ 'list' => 'ListView',
]);
diff --git a/extensions/gii/generators/crud/templates/controller.php b/extensions/gii/generators/crud/templates/controller.php
index 3b8c10cdc77..033bbf20b65 100644
--- a/extensions/gii/generators/crud/templates/controller.php
+++ b/extensions/gii/generators/crud/templates/controller.php
@@ -14,7 +14,7 @@
$modelClass = StringHelper::basename($generator->modelClass);
$searchModelClass = StringHelper::basename($generator->searchModelClass);
if ($modelClass === $searchModelClass) {
- $searchModelAlias = $searchModelClass . 'Search';
+ $searchModelAlias = $searchModelClass . 'Search';
}
/** @var ActiveRecordInterface $class */
@@ -41,121 +41,122 @@
*/
class = $controllerClass ?> extends = StringHelper::basename($generator->baseControllerClass) . "\n" ?>
{
- public function behaviors()
- {
- return [
- 'verbs' => [
- 'class' => VerbFilter::className(),
- 'actions' => [
- 'delete' => ['post'],
- ],
- ],
- ];
- }
-
- /**
- * Lists all = $modelClass ?> models.
- * @return mixed
- */
- public function actionIndex()
- {
- $searchModel = new = isset($searchModelAlias) ? $searchModelAlias : $searchModelClass ?>;
- $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams());
-
- return $this->render('index', [
- 'dataProvider' => $dataProvider,
- 'searchModel' => $searchModel,
- ]);
- }
-
- /**
- * Displays a single = $modelClass ?> model.
- * = implode("\n\t * ", $actionParamComments) . "\n" ?>
- * @return mixed
- */
- public function actionView(= $actionParams ?>)
- {
- return $this->render('view', [
- 'model' => $this->findModel(= $actionParams ?>),
- ]);
- }
-
- /**
- * Creates a new = $modelClass ?> model.
- * If creation is successful, the browser will be redirected to the 'view' page.
- * @return mixed
- */
- public function actionCreate()
- {
- $model = new = $modelClass ?>;
-
- if ($model->load(Yii::$app->request->post()) && $model->save()) {
- return $this->redirect(['view', = $urlParams ?>]);
- } else {
- return $this->render('create', [
- 'model' => $model,
- ]);
- }
- }
-
- /**
- * Updates an existing = $modelClass ?> model.
- * If update is successful, the browser will be redirected to the 'view' page.
- * = implode("\n\t * ", $actionParamComments) . "\n" ?>
- * @return mixed
- */
- public function actionUpdate(= $actionParams ?>)
- {
- $model = $this->findModel(= $actionParams ?>);
-
- if ($model->load(Yii::$app->request->post()) && $model->save()) {
- return $this->redirect(['view', = $urlParams ?>]);
- } else {
- return $this->render('update', [
- 'model' => $model,
- ]);
- }
- }
-
- /**
- * Deletes an existing = $modelClass ?> model.
- * If deletion is successful, the browser will be redirected to the 'index' page.
- * = implode("\n\t * ", $actionParamComments) . "\n" ?>
- * @return mixed
- */
- public function actionDelete(= $actionParams ?>)
- {
- $this->findModel(= $actionParams ?>)->delete();
- return $this->redirect(['index']);
- }
-
- /**
- * Finds the = $modelClass ?> model based on its primary key value.
- * If the model is not found, a 404 HTTP exception will be thrown.
- * = implode("\n\t * ", $actionParamComments) . "\n" ?>
- * @return = $modelClass ?> the loaded model
- * @throws NotFoundHttpException if the model cannot be found
- */
- protected function findModel(= $actionParams ?>)
- {
+ public function behaviors()
+ {
+ return [
+ 'verbs' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['post'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Lists all = $modelClass ?> models.
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ $searchModel = new = isset($searchModelAlias) ? $searchModelAlias : $searchModelClass ?>;
+ $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams());
+
+ return $this->render('index', [
+ 'dataProvider' => $dataProvider,
+ 'searchModel' => $searchModel,
+ ]);
+ }
+
+ /**
+ * Displays a single = $modelClass ?> model.
+ * = implode("\n\t * ", $actionParamComments) . "\n" ?>
+ * @return mixed
+ */
+ public function actionView(= $actionParams ?>)
+ {
+ return $this->render('view', [
+ 'model' => $this->findModel(= $actionParams ?>),
+ ]);
+ }
+
+ /**
+ * Creates a new = $modelClass ?> model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return mixed
+ */
+ public function actionCreate()
+ {
+ $model = new = $modelClass ?>;
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', = $urlParams ?>]);
+ } else {
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing = $modelClass ?> model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * = implode("\n\t * ", $actionParamComments) . "\n" ?>
+ * @return mixed
+ */
+ public function actionUpdate(= $actionParams ?>)
+ {
+ $model = $this->findModel(= $actionParams ?>);
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', = $urlParams ?>]);
+ } else {
+ return $this->render('update', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing = $modelClass ?> model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * = implode("\n\t * ", $actionParamComments) . "\n" ?>
+ * @return mixed
+ */
+ public function actionDelete(= $actionParams ?>)
+ {
+ $this->findModel(= $actionParams ?>)->delete();
+
+ return $this->redirect(['index']);
+ }
+
+ /**
+ * Finds the = $modelClass ?> model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * = implode("\n\t * ", $actionParamComments) . "\n" ?>
+ * @return = $modelClass ?> the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel(= $actionParams ?>)
+ {
\$$pk";
- }
- $condition = '[' . implode(', ', $condition) . ']';
- $nullCheck = '';
+ $condition = [];
+ foreach ($pks as $pk) {
+ $condition[] = "'$pk' => \$$pk";
+ }
+ $condition = '[' . implode(', ', $condition) . ']';
+ $nullCheck = '';
}
?>
- if (= $nullCheck ?>($model = = $modelClass ?>::find(= $condition ?>)) !== null) {
- return $model;
- } else {
- throw new NotFoundHttpException('The requested page does not exist.');
- }
- }
+ if (= $nullCheck ?>($model = = $modelClass ?>::find(= $condition ?>)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
}
diff --git a/extensions/gii/generators/crud/templates/search.php b/extensions/gii/generators/crud/templates/search.php
index 986250e9aab..8e8bdd1ea4f 100644
--- a/extensions/gii/generators/crud/templates/search.php
+++ b/extensions/gii/generators/crud/templates/search.php
@@ -12,7 +12,7 @@
$modelClass = StringHelper::basename($generator->modelClass);
$searchModelClass = StringHelper::basename($generator->searchModelClass);
if ($modelClass === $searchModelClass) {
- $modelAlias = $modelClass . 'Model';
+ $modelAlias = $modelClass . 'Model';
}
$rules = $generator->generateSearchRules();
$labels = $generator->generateSearchLabels();
@@ -33,59 +33,59 @@
*/
class = $searchModelClass ?> extends Model
{
- public $= implode(";\n\tpublic $", $searchAttributes) ?>;
+ public $= implode(";\n\tpublic $", $searchAttributes) ?>;
- public function rules()
- {
- return [
- = implode(",\n\t\t\t", $rules) ?>,
- ];
- }
+ public function rules()
+ {
+ return [
+ = implode(",\n\t\t\t", $rules) ?>,
+ ];
+ }
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
$label): ?>
- = "'$name' => '" . addslashes($label) . "',\n" ?>
+ = "'$name' => '" . addslashes($label) . "',\n" ?>
- ];
- }
+ ];
+ }
- public function search($params)
- {
- $query = = isset($modelAlias) ? $modelAlias : $modelClass ?>::find();
- $dataProvider = new ActiveDataProvider([
- 'query' => $query,
- ]);
+ public function search($params)
+ {
+ $query = = isset($modelAlias) ? $modelAlias : $modelClass ?>::find();
+ $dataProvider = new ActiveDataProvider([
+ 'query' => $query,
+ ]);
- if (!($this->load($params) && $this->validate())) {
- return $dataProvider;
- }
+ if (!($this->load($params) && $this->validate())) {
+ return $dataProvider;
+ }
- = implode("\n\t\t", $searchConditions) ?>
+ = implode("\n\t\t", $searchConditions) ?>
- return $dataProvider;
- }
+ return $dataProvider;
+ }
- protected function addCondition($query, $attribute, $partialMatch = false)
- {
- if (($pos = strrpos($attribute, '.')) !== false) {
- $modelAttribute = substr($attribute, $pos + 1);
- } else {
- $modelAttribute = $attribute;
- }
+ protected function addCondition($query, $attribute, $partialMatch = false)
+ {
+ if (($pos = strrpos($attribute, '.')) !== false) {
+ $modelAttribute = substr($attribute, $pos + 1);
+ } else {
+ $modelAttribute = $attribute;
+ }
- $value = $this->$modelAttribute;
- if (trim($value) === '') {
- return;
- }
- if ($partialMatch) {
- $query->andWhere(['like', $attribute, $value]);
- } else {
- $query->andWhere([$attribute => $value]);
- }
- }
+ $value = $this->$modelAttribute;
+ if (trim($value) === '') {
+ return;
+ }
+ if ($partialMatch) {
+ $query->andWhere(['like', $attribute, $value]);
+ } else {
+ $query->andWhere([$attribute => $value]);
+ }
+ }
}
diff --git a/extensions/gii/generators/crud/templates/views/_form.php b/extensions/gii/generators/crud/templates/views/_form.php
index 52538d50bcb..67136d8fd20 100644
--- a/extensions/gii/generators/crud/templates/views/_form.php
+++ b/extensions/gii/generators/crud/templates/views/_form.php
@@ -12,7 +12,7 @@
$model = new $generator->modelClass;
$safeAttributes = $model->safeAttributes();
if (empty($safeAttributes)) {
- $safeAttributes = $model->attributes();
+ $safeAttributes = $model->attributes();
}
echo "modelClass)) ?>-form">
- = "$form = ActiveForm::begin(); ?>
+ = "$form = ActiveForm::begin(); ?>
generateActiveField($attribute) . " ?>\n\n";
+ echo "\t\t= " . $generator->generateActiveField($attribute) . " ?>\n\n";
} ?>
-
diff --git a/extensions/gii/generators/extension/Generator.php b/extensions/gii/generators/extension/Generator.php
index be47fbc9a1d..f6a1c9d5a67 100644
--- a/extensions/gii/generators/extension/Generator.php
+++ b/extensions/gii/generators/extension/Generator.php
@@ -22,124 +22,124 @@
*/
class Generator extends \yii\gii\Generator
{
- public $vendorName;
- public $packageName = "yii2-";
- public $namespace;
- public $type = "yii2-extension";
- public $keywords = "yii2,extension";
- public $title;
- public $description;
- public $outputPath = "@app/runtime/tmp-extensions";
- public $license;
- public $authorName;
- public $authorEmail;
+ public $vendorName;
+ public $packageName = "yii2-";
+ public $namespace;
+ public $type = "yii2-extension";
+ public $keywords = "yii2,extension";
+ public $title;
+ public $description;
+ public $outputPath = "@app/runtime/tmp-extensions";
+ public $license;
+ public $authorName;
+ public $authorEmail;
- /**
- * @inheritdoc
- */
- public function getName()
- {
- return 'Extension Generator';
- }
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return 'Extension Generator';
+ }
- /**
- * @inheritdoc
- */
- public function getDescription()
- {
- return 'This generator helps you to generate the files needed by a Yii extension.';
- }
+ /**
+ * @inheritdoc
+ */
+ public function getDescription()
+ {
+ return 'This generator helps you to generate the files needed by a Yii extension.';
+ }
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return array_merge(
- parent::rules(),
- [
- [['vendorName', 'packageName'], 'filter', 'filter' => 'trim'],
- [
- [
- 'vendorName',
- 'packageName',
- 'namespace',
- 'type',
- 'license',
- 'title',
- 'description',
- 'authorName',
- 'authorEmail',
- 'outputPath'
- ],
- 'required'
- ],
- [['keywords'], 'safe'],
- [['authorEmail'], 'email'],
- [
- ['vendorName', 'packageName'],
- 'match',
- 'pattern' => '/^[a-z0-9\-\.]+$/',
- 'message' => 'Only lowercase word characters, dashes and dots are allowed.'
- ],
- [
- ['namespace'],
- 'match',
- 'pattern' => '/^[a-zA-Z0-9\\\]+\\\$/',
- 'message' => 'Only letters, numbers and backslashes are allowed. PSR-4 namespaces must end with a namespace separator.'
- ],
- ]
- );
- }
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return array_merge(
+ parent::rules(),
+ [
+ [['vendorName', 'packageName'], 'filter', 'filter' => 'trim'],
+ [
+ [
+ 'vendorName',
+ 'packageName',
+ 'namespace',
+ 'type',
+ 'license',
+ 'title',
+ 'description',
+ 'authorName',
+ 'authorEmail',
+ 'outputPath'
+ ],
+ 'required'
+ ],
+ [['keywords'], 'safe'],
+ [['authorEmail'], 'email'],
+ [
+ ['vendorName', 'packageName'],
+ 'match',
+ 'pattern' => '/^[a-z0-9\-\.]+$/',
+ 'message' => 'Only lowercase word characters, dashes and dots are allowed.'
+ ],
+ [
+ ['namespace'],
+ 'match',
+ 'pattern' => '/^[a-zA-Z0-9\\\]+\\\$/',
+ 'message' => 'Only letters, numbers and backslashes are allowed. PSR-4 namespaces must end with a namespace separator.'
+ ],
+ ]
+ );
+ }
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'vendorName' => 'Vendor Name',
- 'packageName' => 'Package Name',
- 'license' => 'License',
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'vendorName' => 'Vendor Name',
+ 'packageName' => 'Package Name',
+ 'license' => 'License',
+ ];
+ }
- /**
- * @inheritdoc
- */
- public function hints()
- {
- return [
- 'vendorName' => 'This refers to the name of the publisher, your GitHub user name is usually a good choice, eg. myself.',
- 'packageName' => 'This is the name of the extension on packagist, eg. yii2-foobar.',
- 'namespace' => 'PSR-4, eg. myself\foobar\ This will be added to your autoloading by composer. Do not use yii or yii2 in the namespace.',
- 'keywords' => 'Comma separated keywords for this extension.',
- 'outputPath' => 'The temporary location of the generated files.',
- 'title' => 'A more descriptive name of your application for the README file.',
- 'description' => 'A sentence or subline describing the main purpose of the extension.',
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ public function hints()
+ {
+ return [
+ 'vendorName' => 'This refers to the name of the publisher, your GitHub user name is usually a good choice, eg. myself.',
+ 'packageName' => 'This is the name of the extension on packagist, eg. yii2-foobar.',
+ 'namespace' => 'PSR-4, eg. myself\foobar\ This will be added to your autoloading by composer. Do not use yii or yii2 in the namespace.',
+ 'keywords' => 'Comma separated keywords for this extension.',
+ 'outputPath' => 'The temporary location of the generated files.',
+ 'title' => 'A more descriptive name of your application for the README file.',
+ 'description' => 'A sentence or subline describing the main purpose of the extension.',
+ ];
+ }
- /**
- * @inheritdoc
- */
- public function stickyAttributes()
- {
- return ['vendorName', 'outputPath', 'authorName', 'authorEmail'];
- }
+ /**
+ * @inheritdoc
+ */
+ public function stickyAttributes()
+ {
+ return ['vendorName', 'outputPath', 'authorName', 'authorEmail'];
+ }
- /**
- * @inheritdoc
- */
- public function successMessage()
- {
- $outputPath = realpath(\Yii::getAlias($this->outputPath));
- $output1 = <<outputPath));
+ $output1 = <<The extension has been generated successfully.
To enable it in your application, you need to create a git repository
and require it via composer.
EOD;
- $code1 = <<packageName}
git init
@@ -148,121 +148,125 @@ public function successMessage()
git remote add origin https://path.to/your/repo
git push -u origin master
EOD;
- $output2 = <<The next step is just for initial development, skip it if you directly publish the extension on packagist.org
Add the newly created repo to your composer.json.
EOD;
- $code2 = <<Note: You may use the url file://{$outputPath}/{$this->packageName} for testing.
Require the package with composer
EOD;
- $code3 = <<vendorName}/{$this->packageName}:dev-master
EOD;
- $output4 = <<And use it in your application.
EOD;
- $code4 = <<namespace}AutoloadExample::widget();
EOD;
- $output5 = <<When you have finished development register your extension at packagist.org.
EOD;
- $return = $output1 . '
';
+ $return .= $output5;
- /**
- * @inheritdoc
- */
- public function requiredTemplates()
- {
- return ['composer.json', 'AutoloadExample.php', 'README.md'];
- }
+ return $return;
+ }
- /**
- * @inheritdoc
- */
- public function generate()
- {
- $files = [];
- $modulePath = $this->getOutputPath();
- $files[] = new CodeFile(
- $modulePath . '/' . $this->packageName . '/composer.json',
- $this->render("composer.json")
- );
- $files[] = new CodeFile(
- $modulePath . '/' . $this->packageName . '/AutoloadExample.php',
- $this->render("AutoloadExample.php")
- );
- $files[] = new CodeFile(
- $modulePath . '/' . $this->packageName . '/README.md',
- $this->render("README.md")
- );
- return $files;
- }
+ /**
+ * @inheritdoc
+ */
+ public function requiredTemplates()
+ {
+ return ['composer.json', 'AutoloadExample.php', 'README.md'];
+ }
- /**
- * @return boolean the directory that contains the module class
- */
- public function getOutputPath()
- {
- return Yii::getAlias($this->outputPath);
- }
+ /**
+ * @inheritdoc
+ */
+ public function generate()
+ {
+ $files = [];
+ $modulePath = $this->getOutputPath();
+ $files[] = new CodeFile(
+ $modulePath . '/' . $this->packageName . '/composer.json',
+ $this->render("composer.json")
+ );
+ $files[] = new CodeFile(
+ $modulePath . '/' . $this->packageName . '/AutoloadExample.php',
+ $this->render("AutoloadExample.php")
+ );
+ $files[] = new CodeFile(
+ $modulePath . '/' . $this->packageName . '/README.md',
+ $this->render("README.md")
+ );
- /**
- * @return string a json encoded array with the given keywords
- */
- public function getKeywordsArrayJson()
- {
- return json_encode(explode(',', $this->keywords));
- }
+ return $files;
+ }
- /**
- * @return array options for type drop-down
- */
- public function optsType()
- {
- $licenses = [
- 'yii2-extension',
- 'library',
- ];
- return array_combine($licenses, $licenses);
- }
+ /**
+ * @return boolean the directory that contains the module class
+ */
+ public function getOutputPath()
+ {
+ return Yii::getAlias($this->outputPath);
+ }
- /**
- * @return array options for license drop-down
- */
- public function optsLicense()
- {
- $licenses = [
- 'Apache-2.0',
- 'BSD-2-Clause',
- 'BSD-3-Clause',
- 'BSD-4-Clause',
- 'GPL-2.0',
- 'GPL-2.0+',
- 'GPL-3.0',
- 'GPL-3.0+',
- 'LGPL-2.1',
- 'LGPL-2.1+',
- 'LGPL-3.0',
- 'LGPL-3.0+',
- 'MIT'
- ];
- return array_combine($licenses, $licenses);
- }
+ /**
+ * @return string a json encoded array with the given keywords
+ */
+ public function getKeywordsArrayJson()
+ {
+ return json_encode(explode(',', $this->keywords));
+ }
+
+ /**
+ * @return array options for type drop-down
+ */
+ public function optsType()
+ {
+ $licenses = [
+ 'yii2-extension',
+ 'library',
+ ];
+
+ return array_combine($licenses, $licenses);
+ }
+
+ /**
+ * @return array options for license drop-down
+ */
+ public function optsLicense()
+ {
+ $licenses = [
+ 'Apache-2.0',
+ 'BSD-2-Clause',
+ 'BSD-3-Clause',
+ 'BSD-4-Clause',
+ 'GPL-2.0',
+ 'GPL-2.0+',
+ 'GPL-3.0',
+ 'GPL-3.0+',
+ 'LGPL-2.1',
+ 'LGPL-2.1+',
+ 'LGPL-3.0',
+ 'LGPL-3.0+',
+ 'MIT'
+ ];
+
+ return array_combine($licenses, $licenses);
+ }
}
diff --git a/extensions/gii/generators/extension/form.php b/extensions/gii/generators/extension/form.php
index 98a250cafc1..b5b1ac9fc79 100644
--- a/extensions/gii/generators/extension/form.php
+++ b/extensions/gii/generators/extension/form.php
@@ -6,22 +6,22 @@
*/
?>
- Please read the
+ Please read the
= \yii\helpers\Html::a('Extension Guidelines', 'https://github.com/yiisoft/yii2/blob/master/docs/guide/extensions.md', ['target'=>'new']) ?>
- before creating an extension.
+ before creating an extension.
diff --git a/extensions/gii/generators/extension/templates/AutoloadExample.php b/extensions/gii/generators/extension/templates/AutoloadExample.php
index 194ba720f69..96ff3de702d 100644
--- a/extensions/gii/generators/extension/templates/AutoloadExample.php
+++ b/extensions/gii/generators/extension/templates/AutoloadExample.php
@@ -7,8 +7,10 @@
namespace = substr($generator->namespace, 0, -1) ?>;
-class AutoloadExample extends \yii\base\widget {
- function run() {
+class AutoloadExample extends \yii\base\widget
+{
+ public function run()
+ {
return "Hello!";
}
}
diff --git a/extensions/gii/generators/form/Generator.php b/extensions/gii/generators/form/Generator.php
index 9140d71d6b9..fe29d294809 100644
--- a/extensions/gii/generators/form/Generator.php
+++ b/extensions/gii/generators/form/Generator.php
@@ -21,134 +21,136 @@
*/
class Generator extends \yii\gii\Generator
{
- public $modelClass;
- public $viewPath = '@app/views';
- public $viewName;
- public $scenarioName;
-
-
- /**
- * @inheritdoc
- */
- public function getName()
- {
- return 'Form Generator';
- }
-
- /**
- * @inheritdoc
- */
- public function getDescription()
- {
- return 'This generator generates a view script file that displays a form to collect input for the specified model class.';
- }
-
- /**
- * @inheritdoc
- */
- public function generate()
- {
- $files = [];
- $files[] = new CodeFile(
- Yii::getAlias($this->viewPath) . '/' . $this->viewName . '.php',
- $this->render('form.php')
- );
- return $files;
- }
-
- /**
- * @inheritdoc
- */
- public function rules()
- {
- return array_merge(parent::rules(), [
- [['modelClass', 'viewName', 'scenarioName', 'viewPath'], 'filter', 'filter' => 'trim'],
- [['modelClass', 'viewName', 'viewPath'], 'required'],
- [['modelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
- [['modelClass'], 'validateClass', 'params' => ['extends' => Model::className()]],
- [['viewName'], 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'],
- [['viewPath'], 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'],
- [['viewPath'], 'validateViewPath'],
- [['scenarioName'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'],
- ]);
- }
-
- /**
- * @inheritdoc
- */
- public function attributeLabels()
- {
- return [
- 'modelClass' => 'Model Class',
- 'viewName' => 'View Name',
- 'viewPath' => 'View Path',
- 'scenarioName' => 'Scenario',
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function requiredTemplates()
- {
- return ['form.php', 'action.php'];
- }
-
- /**
- * @inheritdoc
- */
- public function stickyAttributes()
- {
- return ['viewPath', 'scenarioName'];
- }
-
- /**
- * @inheritdoc
- */
- public function hints()
- {
- return [
- 'modelClass' => 'This is the model class for collecting the form input. You should provide a fully qualified class name, e.g., app\models\Post.',
- 'viewName' => 'This is the view name with respect to the view path. For example, site/index would generate a site/index.php view file under the view path.',
- 'viewPath' => 'This is the root view path to keep the generated view files. You may provide either a directory or a path alias, e.g., @app/views.',
- 'scenarioName' => 'This is the scenario to be used by the model when collecting the form input. If empty, the default scenario will be used.',
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function successMessage()
- {
- $code = highlight_string($this->render('action.php'), true);
- return <<viewPath) . '/' . $this->viewName . '.php',
+ $this->render('form.php')
+ );
+
+ return $files;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return array_merge(parent::rules(), [
+ [['modelClass', 'viewName', 'scenarioName', 'viewPath'], 'filter', 'filter' => 'trim'],
+ [['modelClass', 'viewName', 'viewPath'], 'required'],
+ [['modelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
+ [['modelClass'], 'validateClass', 'params' => ['extends' => Model::className()]],
+ [['viewName'], 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'],
+ [['viewPath'], 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'],
+ [['viewPath'], 'validateViewPath'],
+ [['scenarioName'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'],
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'modelClass' => 'Model Class',
+ 'viewName' => 'View Name',
+ 'viewPath' => 'View Path',
+ 'scenarioName' => 'Scenario',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function requiredTemplates()
+ {
+ return ['form.php', 'action.php'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function stickyAttributes()
+ {
+ return ['viewPath', 'scenarioName'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function hints()
+ {
+ return [
+ 'modelClass' => 'This is the model class for collecting the form input. You should provide a fully qualified class name, e.g., app\models\Post.',
+ 'viewName' => 'This is the view name with respect to the view path. For example, site/index would generate a site/index.php view file under the view path.',
+ 'viewPath' => 'This is the root view path to keep the generated view files. You may provide either a directory or a path alias, e.g., @app/views.',
+ 'scenarioName' => 'This is the scenario to be used by the model when collecting the form input. If empty, the default scenario will be used.',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function successMessage()
+ {
+ $code = highlight_string($this->render('action.php'), true);
+
+ return <<The form has been generated successfully.
You may add the following code in an appropriate controller class to invoke the view:
$code
EOD;
- }
-
- /**
- * Validates [[viewPath]] to make sure it is a valid path or path alias and exists.
- */
- public function validateViewPath()
- {
- $path = Yii::getAlias($this->viewPath, false);
- if ($path === false || !is_dir($path)) {
- $this->addError('viewPath', 'View path does not exist.');
- }
- }
-
- /**
- * @return array list of safe attributes of [[modelClass]]
- */
- public function getModelAttributes()
- {
- /** @var Model $model */
- $model = new $this->modelClass;
- if (!empty($this->scenarioName)) {
- $model->setScenario($this->scenarioName);
- }
- return $model->safeAttributes();
- }
+ }
+
+ /**
+ * Validates [[viewPath]] to make sure it is a valid path or path alias and exists.
+ */
+ public function validateViewPath()
+ {
+ $path = Yii::getAlias($this->viewPath, false);
+ if ($path === false || !is_dir($path)) {
+ $this->addError('viewPath', 'View path does not exist.');
+ }
+ }
+
+ /**
+ * @return array list of safe attributes of [[modelClass]]
+ */
+ public function getModelAttributes()
+ {
+ /** @var Model $model */
+ $model = new $this->modelClass;
+ if (!empty($this->scenarioName)) {
+ $model->setScenario($this->scenarioName);
+ }
+
+ return $model->safeAttributes();
+ }
}
diff --git a/extensions/gii/generators/form/templates/action.php b/extensions/gii/generators/form/templates/action.php
index c7e1799548e..f9587bcdaf3 100644
--- a/extensions/gii/generators/form/templates/action.php
+++ b/extensions/gii/generators/form/templates/action.php
@@ -14,15 +14,16 @@
public function action= Inflector::id2camel(trim(basename($generator->viewName), '_')) ?>()
{
- $model = new = $generator->modelClass ?>= empty($generator->scenarioName) ? "" : "(['scenario' => '{$generator->scenarioName}'])" ?>;
+ $model = new = $generator->modelClass ?>= empty($generator->scenarioName) ? "" : "(['scenario' => '{$generator->scenarioName}'])" ?>;
- if ($model->load(Yii::$app->request->post())) {
- if ($model->validate()) {
- // form inputs are valid, do something here
- return;
- }
- }
- return $this->render('= $generator->viewName ?>', [
- 'model' => $model,
- ]);
+ if ($model->load(Yii::$app->request->post())) {
+ if ($model->validate()) {
+ // form inputs are valid, do something here
+ return;
+ }
+ }
+
+ return $this->render('= $generator->viewName ?>', [
+ 'model' => $model,
+ ]);
}
diff --git a/extensions/gii/generators/form/templates/form.php b/extensions/gii/generators/form/templates/form.php
index b30570c0df3..97b0d79b977 100644
--- a/extensions/gii/generators/form/templates/form.php
+++ b/extensions/gii/generators/form/templates/form.php
@@ -21,15 +21,15 @@
diff --git a/extensions/gii/generators/module/templates/controller.php b/extensions/gii/generators/module/templates/controller.php
index 018450c208d..ff7a095b20a 100644
--- a/extensions/gii/generators/module/templates/controller.php
+++ b/extensions/gii/generators/module/templates/controller.php
@@ -14,8 +14,8 @@
class DefaultController extends Controller
{
- public function actionIndex()
- {
- return $this->render('index');
- }
+ public function actionIndex()
+ {
+ return $this->render('index');
+ }
}
diff --git a/extensions/gii/generators/module/templates/module.php b/extensions/gii/generators/module/templates/module.php
index 72b32be8c5b..dabbc745b58 100644
--- a/extensions/gii/generators/module/templates/module.php
+++ b/extensions/gii/generators/module/templates/module.php
@@ -15,15 +15,14 @@
namespace = $ns ?>;
-
class = $className ?> extends \yii\base\Module
{
- public $controllerNamespace = '= $generator->getControllerNamespace() ?>';
+ public $controllerNamespace = '= $generator->getControllerNamespace() ?>';
- public function init()
- {
- parent::init();
+ public function init()
+ {
+ parent::init();
- // custom initialization code goes here
- }
+ // custom initialization code goes here
+ }
}
diff --git a/extensions/gii/generators/module/templates/view.php b/extensions/gii/generators/module/templates/view.php
index f5f42487c6b..7655ae49c02 100644
--- a/extensions/gii/generators/module/templates/view.php
+++ b/extensions/gii/generators/module/templates/view.php
@@ -5,14 +5,14 @@
*/
?>
-
= "= " ?>$this->context->action->uniqueId ?>
-
- This is the view content for action "= "= " ?>$this->context->action->id ?>".
- The action belongs to the controller "= "= " ?>get_class($this->context) ?>"
- in the "= "= " ?>$this->context->module->id ?>" module.
-
-
- You may customize this page by editing the following file:
- = "= " ?>__FILE__ ?>
-
+
= "= " ?>$this->context->action->uniqueId ?>
+
+ This is the view content for action "= "= " ?>$this->context->action->id ?>".
+ The action belongs to the controller "= "= " ?>get_class($this->context) ?>"
+ in the "= "= " ?>$this->context->module->id ?>" module.
+
+
+ You may customize this page by editing the following file:
+ = "= " ?>__FILE__ ?>
+
- = $this->renderFile($generator->formView(), [
- 'generator' => $generator,
- 'form' => $form,
- ]) ?>
- = $form->field($generator, 'template')->sticky()
- ->label('Code Template')
- ->dropDownList($templates)->hint('
- Please select which set of the templates should be used to generated the code.
- ') ?>
-
+ = $this->renderFile($generator->formView(), [
+ 'generator' => $generator,
+ 'form' => $form,
+ ]) ?>
+ = $form->field($generator, 'template')->sticky()
+ ->label('Code Template')
+ ->dropDownList($templates)->hint('
+ Please select which set of the templates should be used to generated the code.
+ ') ?>
+
endBody() ?>
diff --git a/extensions/imagine/BaseImage.php b/extensions/imagine/BaseImage.php
index bf6ed773669..527e7e6be5a 100644
--- a/extensions/imagine/BaseImage.php
+++ b/extensions/imagine/BaseImage.php
@@ -29,227 +29,229 @@
*/
class BaseImage
{
- /**
- * GD2 driver definition for Imagine implementation using the GD library.
- */
- const DRIVER_GD2 = 'gd2';
- /**
- * imagick driver definition.
- */
- const DRIVER_IMAGICK = 'imagick';
- /**
- * gmagick driver definition.
- */
- const DRIVER_GMAGICK = 'gmagick';
- /**
- * @var array|string the driver to use. This can be either a single driver name or an array of driver names.
- * If the latter, the first available driver will be used.
- */
- public static $driver = [self::DRIVER_GMAGICK, self::DRIVER_IMAGICK, self::DRIVER_GD2];
-
- /**
- * @var ImagineInterface instance.
- */
- private static $_imagine;
-
- /**
- * Returns the `Imagine` object that supports various image manipulations.
- * @return ImagineInterface the `Imagine` object
- */
- public static function getImagine()
- {
- if (self::$_imagine === null) {
- self::$_imagine = static::createImagine();
- }
- return self::$_imagine;
- }
-
- /**
- * @param ImagineInterface $imagine the `Imagine` object.
- */
- public static function setImagine($imagine)
- {
- self::$_imagine = $imagine;
- }
-
- /**
- * Creates an `Imagine` object based on the specified [[driver]].
- * @return ImagineInterface the new `Imagine` object
- * @throws InvalidConfigException if [[driver]] is unknown or the system doesn't support any [[driver]].
- */
- protected static function createImagine()
- {
- foreach ((array)static::$driver as $driver) {
- switch ($driver) {
- case self::DRIVER_GMAGICK:
- if (class_exists('Gmagick', false)) {
- return new \Imagine\Gmagick\Imagine();
- }
- break;
- case self::DRIVER_IMAGICK:
- if (class_exists('Imagick', false)) {
- return new \Imagine\Imagick\Imagine();
- }
- break;
- case self::DRIVER_GD2:
- if (function_exists('gd_info')) {
- return new \Imagine\Gd\Imagine();
- }
- break;
- default:
- throw new InvalidConfigException("Unknown driver: $driver");
- }
- }
- throw new InvalidConfigException("Your system does not support any of these drivers: " . implode(',', (array)static::$driver));
- }
-
- /**
- * Crops an image.
- *
- * For example,
- *
- * ~~~
- * $obj->crop('path\to\image.jpg', 200, 200, [5, 5]);
- *
- * $point = new \Imagine\Image\Point(5, 5);
- * $obj->crop('path\to\image.jpg', 200, 200, $point);
- * ~~~
- *
- * @param string $filename the image file path or path alias.
- * @param integer $width the crop width
- * @param integer $height the crop height
- * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates.
- * @return ImageInterface
- * @throws InvalidParamException if the `$start` parameter is invalid
- */
- public static function crop($filename, $width, $height, array $start = [0, 0])
- {
- if (!isset($start[0], $start[1])) {
- throw new InvalidParamException('$start must be an array of two elements.');
- }
-
- return static::getImagine()
- ->open(Yii::getAlias($filename))
- ->copy()
- ->crop(new Point($start[0], $start[1]), new Box($width, $height));
- }
-
- /**
- * Creates a thumbnail image. The function differs from [[\Imagine\Image\ImageInterface::thumbnail()]] function that
- * it keeps the aspect ratio of the image.
- * @param string $filename the image file path or path alias.
- * @param integer $width the width in pixels to create the thumbnail
- * @param integer $height the height in pixels to create the thumbnail
- * @param string $mode
- * @return ImageInterface
- */
- public static function thumbnail($filename, $width, $height, $mode = ManipulatorInterface::THUMBNAIL_OUTBOUND)
- {
- $box = new Box($width, $height);
- $img = static::getImagine()->open(Yii::getAlias($filename));
-
- if (($img->getSize()->getWidth() <= $box->getWidth() && $img->getSize()->getHeight() <= $box->getHeight()) || (!$box->getWidth() && !$box->getHeight())) {
- return $img->copy();
- }
-
- $img = $img->thumbnail($box, $mode);
-
- // create empty image to preserve aspect ratio of thumbnail
- $thumb = static::getImagine()->create($box, new Color('FFF', 100));
-
- // calculate points
- $size = $img->getSize();
-
- $startX = 0;
- $startY = 0;
- if ($size->getWidth() < $width) {
- $startX = ceil($width - $size->getWidth()) / 2;
- }
- if ($size->getHeight() < $height) {
- $startY = ceil($height - $size->getHeight()) / 2;
- }
-
- $thumb->paste($img, new Point($startX, $startY));
-
- return $thumb;
- }
-
- /**
- * Adds a watermark to an existing image.
- * @param string $filename the image file path or path alias.
- * @param string $watermarkFilename the file path or path alias of the watermark image.
- * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates.
- * @return ImageInterface
- * @throws InvalidParamException if `$start` is invalid
- */
- public static function watermark($filename, $watermarkFilename, array $start = [0, 0])
- {
- if (!isset($start[0], $start[1])) {
- throw new InvalidParamException('$start must be an array of two elements.');
- }
-
- $img = static::getImagine()->open(Yii::getAlias($filename));
- $watermark = static::getImagine()->open(Yii::getAlias($watermarkFilename));
- $img->paste($watermark, new Point($start[0], $start[1]));
- return $img;
- }
-
- /**
- * Draws a text string on an existing image.
- * @param string $filename the image file path or path alias.
- * @param string $text the text to write to the image
- * @param string $fontFile the file path or path alias
- * @param array $start the starting position of the text. This must be an array with two elements representing `x` and `y` coordinates.
- * @param array $fontOptions the font options. The following options may be specified:
- *
- * - color: The font color. Defaults to "fff".
- * - size: The font size. Defaults to 12.
- * - angle: The angle to use to write the text. Defaults to 0.
- *
- * @return ImageInterface
- * @throws InvalidParamException if `$fontOptions` is invalid
- */
- public static function text($filename, $text, $fontFile, array $start = [0, 0], array $fontOptions = [])
- {
- if (!isset($start[0], $start[1])) {
- throw new InvalidParamException('$start must be an array of two elements.');
- }
-
- $fontSize = ArrayHelper::getValue($fontOptions, 'size', 12);
- $fontColor = ArrayHelper::getValue($fontOptions, 'color', 'fff');
- $fontAngle = ArrayHelper::getValue($fontOptions, 'angle', 0);
-
- $img = static::getImagine()->open(Yii::getAlias($filename));
- $font = static::getImagine()->font(Yii::getAlias($fontFile), $fontSize, new Color($fontColor));
-
- $img->draw()->text($text, $font, new Point($start[0], $start[1]), $fontAngle);
-
- return $img;
- }
-
- /**
- * Adds a frame around of the image. Please note that the image size will increase by `$margin` x 2.
- * @param string $filename the full path to the image file
- * @param integer $margin the frame size to add around the image
- * @param string $color the frame color
- * @param integer $alpha the alpha value of the frame.
- * @return ImageInterface
- */
- public static function frame($filename, $margin = 20, $color = '666', $alpha = 100)
- {
- $img = static::getImagine()->open(Yii::getAlias($filename));
-
- $size = $img->getSize();
-
- $pasteTo = new Point($margin, $margin);
- $padColor = new Color($color, $alpha);
-
- $box = new Box($size->getWidth() + ceil($margin * 2), $size->getHeight() + ceil($margin * 2));
-
- $image = static::getImagine()->create($box, $padColor);
-
- $image->paste($img, $pasteTo);
-
- return $image;
- }
+ /**
+ * GD2 driver definition for Imagine implementation using the GD library.
+ */
+ const DRIVER_GD2 = 'gd2';
+ /**
+ * imagick driver definition.
+ */
+ const DRIVER_IMAGICK = 'imagick';
+ /**
+ * gmagick driver definition.
+ */
+ const DRIVER_GMAGICK = 'gmagick';
+ /**
+ * @var array|string the driver to use. This can be either a single driver name or an array of driver names.
+ * If the latter, the first available driver will be used.
+ */
+ public static $driver = [self::DRIVER_GMAGICK, self::DRIVER_IMAGICK, self::DRIVER_GD2];
+
+ /**
+ * @var ImagineInterface instance.
+ */
+ private static $_imagine;
+
+ /**
+ * Returns the `Imagine` object that supports various image manipulations.
+ * @return ImagineInterface the `Imagine` object
+ */
+ public static function getImagine()
+ {
+ if (self::$_imagine === null) {
+ self::$_imagine = static::createImagine();
+ }
+
+ return self::$_imagine;
+ }
+
+ /**
+ * @param ImagineInterface $imagine the `Imagine` object.
+ */
+ public static function setImagine($imagine)
+ {
+ self::$_imagine = $imagine;
+ }
+
+ /**
+ * Creates an `Imagine` object based on the specified [[driver]].
+ * @return ImagineInterface the new `Imagine` object
+ * @throws InvalidConfigException if [[driver]] is unknown or the system doesn't support any [[driver]].
+ */
+ protected static function createImagine()
+ {
+ foreach ((array) static::$driver as $driver) {
+ switch ($driver) {
+ case self::DRIVER_GMAGICK:
+ if (class_exists('Gmagick', false)) {
+ return new \Imagine\Gmagick\Imagine();
+ }
+ break;
+ case self::DRIVER_IMAGICK:
+ if (class_exists('Imagick', false)) {
+ return new \Imagine\Imagick\Imagine();
+ }
+ break;
+ case self::DRIVER_GD2:
+ if (function_exists('gd_info')) {
+ return new \Imagine\Gd\Imagine();
+ }
+ break;
+ default:
+ throw new InvalidConfigException("Unknown driver: $driver");
+ }
+ }
+ throw new InvalidConfigException("Your system does not support any of these drivers: " . implode(',', (array) static::$driver));
+ }
+
+ /**
+ * Crops an image.
+ *
+ * For example,
+ *
+ * ~~~
+ * $obj->crop('path\to\image.jpg', 200, 200, [5, 5]);
+ *
+ * $point = new \Imagine\Image\Point(5, 5);
+ * $obj->crop('path\to\image.jpg', 200, 200, $point);
+ * ~~~
+ *
+ * @param string $filename the image file path or path alias.
+ * @param integer $width the crop width
+ * @param integer $height the crop height
+ * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates.
+ * @return ImageInterface
+ * @throws InvalidParamException if the `$start` parameter is invalid
+ */
+ public static function crop($filename, $width, $height, array $start = [0, 0])
+ {
+ if (!isset($start[0], $start[1])) {
+ throw new InvalidParamException('$start must be an array of two elements.');
+ }
+
+ return static::getImagine()
+ ->open(Yii::getAlias($filename))
+ ->copy()
+ ->crop(new Point($start[0], $start[1]), new Box($width, $height));
+ }
+
+ /**
+ * Creates a thumbnail image. The function differs from [[\Imagine\Image\ImageInterface::thumbnail()]] function that
+ * it keeps the aspect ratio of the image.
+ * @param string $filename the image file path or path alias.
+ * @param integer $width the width in pixels to create the thumbnail
+ * @param integer $height the height in pixels to create the thumbnail
+ * @param string $mode
+ * @return ImageInterface
+ */
+ public static function thumbnail($filename, $width, $height, $mode = ManipulatorInterface::THUMBNAIL_OUTBOUND)
+ {
+ $box = new Box($width, $height);
+ $img = static::getImagine()->open(Yii::getAlias($filename));
+
+ if (($img->getSize()->getWidth() <= $box->getWidth() && $img->getSize()->getHeight() <= $box->getHeight()) || (!$box->getWidth() && !$box->getHeight())) {
+ return $img->copy();
+ }
+
+ $img = $img->thumbnail($box, $mode);
+
+ // create empty image to preserve aspect ratio of thumbnail
+ $thumb = static::getImagine()->create($box, new Color('FFF', 100));
+
+ // calculate points
+ $size = $img->getSize();
+
+ $startX = 0;
+ $startY = 0;
+ if ($size->getWidth() < $width) {
+ $startX = ceil($width - $size->getWidth()) / 2;
+ }
+ if ($size->getHeight() < $height) {
+ $startY = ceil($height - $size->getHeight()) / 2;
+ }
+
+ $thumb->paste($img, new Point($startX, $startY));
+
+ return $thumb;
+ }
+
+ /**
+ * Adds a watermark to an existing image.
+ * @param string $filename the image file path or path alias.
+ * @param string $watermarkFilename the file path or path alias of the watermark image.
+ * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates.
+ * @return ImageInterface
+ * @throws InvalidParamException if `$start` is invalid
+ */
+ public static function watermark($filename, $watermarkFilename, array $start = [0, 0])
+ {
+ if (!isset($start[0], $start[1])) {
+ throw new InvalidParamException('$start must be an array of two elements.');
+ }
+
+ $img = static::getImagine()->open(Yii::getAlias($filename));
+ $watermark = static::getImagine()->open(Yii::getAlias($watermarkFilename));
+ $img->paste($watermark, new Point($start[0], $start[1]));
+
+ return $img;
+ }
+
+ /**
+ * Draws a text string on an existing image.
+ * @param string $filename the image file path or path alias.
+ * @param string $text the text to write to the image
+ * @param string $fontFile the file path or path alias
+ * @param array $start the starting position of the text. This must be an array with two elements representing `x` and `y` coordinates.
+ * @param array $fontOptions the font options. The following options may be specified:
+ *
+ * - color: The font color. Defaults to "fff".
+ * - size: The font size. Defaults to 12.
+ * - angle: The angle to use to write the text. Defaults to 0.
+ *
+ * @return ImageInterface
+ * @throws InvalidParamException if `$fontOptions` is invalid
+ */
+ public static function text($filename, $text, $fontFile, array $start = [0, 0], array $fontOptions = [])
+ {
+ if (!isset($start[0], $start[1])) {
+ throw new InvalidParamException('$start must be an array of two elements.');
+ }
+
+ $fontSize = ArrayHelper::getValue($fontOptions, 'size', 12);
+ $fontColor = ArrayHelper::getValue($fontOptions, 'color', 'fff');
+ $fontAngle = ArrayHelper::getValue($fontOptions, 'angle', 0);
+
+ $img = static::getImagine()->open(Yii::getAlias($filename));
+ $font = static::getImagine()->font(Yii::getAlias($fontFile), $fontSize, new Color($fontColor));
+
+ $img->draw()->text($text, $font, new Point($start[0], $start[1]), $fontAngle);
+
+ return $img;
+ }
+
+ /**
+ * Adds a frame around of the image. Please note that the image size will increase by `$margin` x 2.
+ * @param string $filename the full path to the image file
+ * @param integer $margin the frame size to add around the image
+ * @param string $color the frame color
+ * @param integer $alpha the alpha value of the frame.
+ * @return ImageInterface
+ */
+ public static function frame($filename, $margin = 20, $color = '666', $alpha = 100)
+ {
+ $img = static::getImagine()->open(Yii::getAlias($filename));
+
+ $size = $img->getSize();
+
+ $pasteTo = new Point($margin, $margin);
+ $padColor = new Color($color, $alpha);
+
+ $box = new Box($size->getWidth() + ceil($margin * 2), $size->getHeight() + ceil($margin * 2));
+
+ $image = static::getImagine()->create($box, $padColor);
+
+ $image->paste($img, $pasteTo);
+
+ return $image;
+ }
}
diff --git a/extensions/jui/Accordion.php b/extensions/jui/Accordion.php
index a265cb87f8a..d4d4dcdbbaa 100644
--- a/extensions/jui/Accordion.php
+++ b/extensions/jui/Accordion.php
@@ -43,85 +43,84 @@
*/
class Accordion extends Widget
{
- /**
- * @var array the HTML attributes for the widget container tag. The following special options are recognized:
- *
- * - tag: string, defaults to "div", the tag name of the container tag of this widget
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array list of collapsible items. Each item can be an array of the following structure:
- *
- * ~~~
- * [
- * 'header' => 'Item header',
- * 'content' => 'Item content',
- * // the HTML attributes of the item header container tag. This will overwrite "headerOptions".
- * 'headerOptions' => [],
- * // the HTML attributes of the item container tag. This will overwrite "itemOptions".
- * 'options' => [],
- * ]
- * ~~~
- */
- public $items = [];
- /**
- * @var array list of HTML attributes for the item container tags. This will be overwritten
- * by the "options" set in individual [[items]]. The following special options are recognized:
- *
- * - tag: string, defaults to "div", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $itemOptions = [];
- /**
- * @var array list of HTML attributes for the item header container tags. This will be overwritten
- * by the "headerOptions" set in individual [[items]]. The following special options are recognized:
- *
- * - tag: string, defaults to "h3", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $headerOptions = [];
+ /**
+ * @var array the HTML attributes for the widget container tag. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div", the tag name of the container tag of this widget
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array list of collapsible items. Each item can be an array of the following structure:
+ *
+ * ~~~
+ * [
+ * 'header' => 'Item header',
+ * 'content' => 'Item content',
+ * // the HTML attributes of the item header container tag. This will overwrite "headerOptions".
+ * 'headerOptions' => [],
+ * // the HTML attributes of the item container tag. This will overwrite "itemOptions".
+ * 'options' => [],
+ * ]
+ * ~~~
+ */
+ public $items = [];
+ /**
+ * @var array list of HTML attributes for the item container tags. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div", the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $itemOptions = [];
+ /**
+ * @var array list of HTML attributes for the item header container tags. This will be overwritten
+ * by the "headerOptions" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "h3", the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $headerOptions = [];
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ $options = $this->options;
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ echo Html::beginTag($tag, $options) . "\n";
+ echo $this->renderItems() . "\n";
+ echo Html::endTag($tag) . "\n";
+ $this->registerWidget('accordion', AccordionAsset::className());
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- $options = $this->options;
- $tag = ArrayHelper::remove($options, 'tag', 'div');
- echo Html::beginTag($tag, $options) . "\n";
- echo $this->renderItems() . "\n";
- echo Html::endTag($tag) . "\n";
- $this->registerWidget('accordion', AccordionAsset::className());
- }
+ /**
+ * Renders collapsible items as specified on [[items]].
+ * @return string the rendering result.
+ * @throws InvalidConfigException.
+ */
+ protected function renderItems()
+ {
+ $items = [];
+ foreach ($this->items as $item) {
+ if (!isset($item['header'])) {
+ throw new InvalidConfigException("The 'header' option is required.");
+ }
+ if (!isset($item['content'])) {
+ throw new InvalidConfigException("The 'content' option is required.");
+ }
+ $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
+ $headerTag = ArrayHelper::remove($headerOptions, 'tag', 'h3');
+ $items[] = Html::tag($headerTag, $item['header'], $headerOptions);
+ $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ $items[] = Html::tag($tag, $item['content'], $options);
+ }
- /**
- * Renders collapsible items as specified on [[items]].
- * @return string the rendering result.
- * @throws InvalidConfigException.
- */
- protected function renderItems()
- {
- $items = [];
- foreach ($this->items as $item) {
- if (!isset($item['header'])) {
- throw new InvalidConfigException("The 'header' option is required.");
- }
- if (!isset($item['content'])) {
- throw new InvalidConfigException("The 'content' option is required.");
- }
- $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
- $headerTag = ArrayHelper::remove($headerOptions, 'tag', 'h3');
- $items[] = Html::tag($headerTag, $item['header'], $headerOptions);
- $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
- $tag = ArrayHelper::remove($options, 'tag', 'div');
- $items[] = Html::tag($tag, $item['content'], $options);
- }
-
- return implode("\n", $items);
- }
+ return implode("\n", $items);
+ }
}
diff --git a/extensions/jui/AccordionAsset.php b/extensions/jui/AccordionAsset.php
index 05c1e20d54d..3a71a703b4a 100644
--- a/extensions/jui/AccordionAsset.php
+++ b/extensions/jui/AccordionAsset.php
@@ -15,12 +15,12 @@
*/
class AccordionAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.accordion.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- 'yii\jui\EffectAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.accordion.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ 'yii\jui\EffectAsset',
+ ];
}
diff --git a/extensions/jui/AutoComplete.php b/extensions/jui/AutoComplete.php
index 93f4332f4ce..6617b2df869 100644
--- a/extensions/jui/AutoComplete.php
+++ b/extensions/jui/AutoComplete.php
@@ -41,25 +41,25 @@
*/
class AutoComplete extends InputWidget
{
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo $this->renderWidget();
- $this->registerWidget('autocomplete', AutoCompleteAsset::className());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderWidget();
+ $this->registerWidget('autocomplete', AutoCompleteAsset::className());
+ }
- /**
- * Renders the AutoComplete widget.
- * @return string the rendering result.
- */
- public function renderWidget()
- {
- if ($this->hasModel()) {
- return Html::activeTextInput($this->model, $this->attribute, $this->options);
- } else {
- return Html::textInput($this->name, $this->value, $this->options);
- }
- }
+ /**
+ * Renders the AutoComplete widget.
+ * @return string the rendering result.
+ */
+ public function renderWidget()
+ {
+ if ($this->hasModel()) {
+ return Html::activeTextInput($this->model, $this->attribute, $this->options);
+ } else {
+ return Html::textInput($this->name, $this->value, $this->options);
+ }
+ }
}
diff --git a/extensions/jui/AutoCompleteAsset.php b/extensions/jui/AutoCompleteAsset.php
index 8e2b3f463aa..4a92c93d637 100644
--- a/extensions/jui/AutoCompleteAsset.php
+++ b/extensions/jui/AutoCompleteAsset.php
@@ -15,12 +15,12 @@
*/
class AutoCompleteAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.autocomplete.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- 'yii\jui\MenuAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.autocomplete.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ 'yii\jui\MenuAsset',
+ ];
}
diff --git a/extensions/jui/ButtonAsset.php b/extensions/jui/ButtonAsset.php
index b00354e4ba3..fb662558c3a 100644
--- a/extensions/jui/ButtonAsset.php
+++ b/extensions/jui/ButtonAsset.php
@@ -15,11 +15,11 @@
*/
class ButtonAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.button.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.button.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/CoreAsset.php b/extensions/jui/CoreAsset.php
index f221f53eccd..7a95d592561 100644
--- a/extensions/jui/CoreAsset.php
+++ b/extensions/jui/CoreAsset.php
@@ -15,14 +15,14 @@
*/
class CoreAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.core.js',
- 'jquery.ui.widget.js',
- 'jquery.ui.position.js',
- 'jquery.ui.mouse.js',
- ];
- public $depends = [
- 'yii\web\JqueryAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.core.js',
+ 'jquery.ui.widget.js',
+ 'jquery.ui.position.js',
+ 'jquery.ui.mouse.js',
+ ];
+ public $depends = [
+ 'yii\web\JqueryAsset',
+ ];
}
diff --git a/extensions/jui/DatePicker.php b/extensions/jui/DatePicker.php
index 9e46624a1ed..83a26d8ef3d 100644
--- a/extensions/jui/DatePicker.php
+++ b/extensions/jui/DatePicker.php
@@ -45,83 +45,82 @@
*/
class DatePicker extends InputWidget
{
- /**
- * @var string the locale ID (eg 'fr', 'de') for the language to be used by the date picker.
- * If this property is empty, then the current application language will be used.
- */
- public $language;
- /**
- * @var boolean If true, shows the widget as an inline calendar and the input as a hidden field.
- */
- public $inline = false;
- /**
- * @var array the HTML attributes for the container tag. This is only used when [[inline]] is true.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $containerOptions = [];
+ /**
+ * @var string the locale ID (eg 'fr', 'de') for the language to be used by the date picker.
+ * If this property is empty, then the current application language will be used.
+ */
+ public $language;
+ /**
+ * @var boolean If true, shows the widget as an inline calendar and the input as a hidden field.
+ */
+ public $inline = false;
+ /**
+ * @var array the HTML attributes for the container tag. This is only used when [[inline]] is true.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $containerOptions = [];
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->inline && !isset($this->containerOptions['id'])) {
+ $this->containerOptions['id'] = $this->options['id'] . '-container';
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->inline && !isset($this->containerOptions['id'])) {
- $this->containerOptions['id'] = $this->options['id'] . '-container';
- }
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderWidget() . "\n";
+ $containerID = $this->inline ? $this->containerOptions['id'] : $this->options['id'];
+ $language = $this->language ? $this->language : Yii::$app->language;
+ if ($language != 'en') {
+ $view = $this->getView();
+ DatePickerRegionalAsset::register($view);
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo $this->renderWidget() . "\n";
- $containerID = $this->inline ? $this->containerOptions['id'] : $this->options['id'];
- $language = $this->language ? $this->language : Yii::$app->language;
- if ($language != 'en') {
- $view = $this->getView();
- DatePickerRegionalAsset::register($view);
+ $options = Json::encode($this->clientOptions);
+ $view->registerJs("$('#{$containerID}').datepicker($.extend({}, $.datepicker.regional['{$language}'], $options));");
- $options = Json::encode($this->clientOptions);
- $view->registerJs("$('#{$containerID}').datepicker($.extend({}, $.datepicker.regional['{$language}'], $options));");
+ $options = $this->clientOptions;
+ $this->clientOptions = false; // the datepicker js widget is already registered
+ $this->registerWidget('datepicker', DatePickerAsset::className(), $containerID);
+ $this->clientOptions = $options;
+ } else {
+ $this->registerWidget('datepicker', DatePickerAsset::className(), $containerID);
+ }
+ }
- $options = $this->clientOptions;
- $this->clientOptions = false; // the datepicker js widget is already registered
- $this->registerWidget('datepicker', DatePickerAsset::className(), $containerID);
- $this->clientOptions = $options;
- } else {
- $this->registerWidget('datepicker', DatePickerAsset::className(), $containerID);
- }
- }
+ /**
+ * Renders the DatePicker widget.
+ * @return string the rendering result.
+ */
+ protected function renderWidget()
+ {
+ $contents = [];
- /**
- * Renders the DatePicker widget.
- * @return string the rendering result.
- */
- protected function renderWidget()
- {
- $contents = [];
+ if ($this->inline === false) {
+ if ($this->hasModel()) {
+ $contents[] = Html::activeTextInput($this->model, $this->attribute, $this->options);
+ } else {
+ $contents[] = Html::textInput($this->name, $this->value, $this->options);
+ }
+ } else {
+ if ($this->hasModel()) {
+ $contents[] = Html::activeHiddenInput($this->model, $this->attribute, $this->options);
+ $this->clientOptions['defaultDate'] = $this->model->{$this->attribute};
+ } else {
+ $contents[] = Html::hiddenInput($this->name, $this->value, $this->options);
+ $this->clientOptions['defaultDate'] = $this->value;
+ }
+ $this->clientOptions['altField'] = '#' . $this->options['id'];
+ $contents[] = Html::tag('div', null, $this->containerOptions);
+ }
- if ($this->inline === false) {
- if ($this->hasModel()) {
- $contents[] = Html::activeTextInput($this->model, $this->attribute, $this->options);
- } else {
- $contents[] = Html::textInput($this->name, $this->value, $this->options);
- }
- } else {
- if ($this->hasModel()) {
- $contents[] = Html::activeHiddenInput($this->model, $this->attribute, $this->options);
- $this->clientOptions['defaultDate'] = $this->model->{$this->attribute};
- } else {
- $contents[] = Html::hiddenInput($this->name, $this->value, $this->options);
- $this->clientOptions['defaultDate'] = $this->value;
- }
- $this->clientOptions['altField'] = '#' . $this->options['id'];
- $contents[] = Html::tag('div', null, $this->containerOptions);
- }
-
- return implode("\n", $contents);
- }
+ return implode("\n", $contents);
+ }
}
diff --git a/extensions/jui/DatePickerAsset.php b/extensions/jui/DatePickerAsset.php
index 68fb486a434..48ff0e7e79c 100644
--- a/extensions/jui/DatePickerAsset.php
+++ b/extensions/jui/DatePickerAsset.php
@@ -15,12 +15,12 @@
*/
class DatePickerAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.datepicker.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- 'yii\jui\EffectAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.datepicker.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ 'yii\jui\EffectAsset',
+ ];
}
diff --git a/extensions/jui/DatePickerRegionalAsset.php b/extensions/jui/DatePickerRegionalAsset.php
index fcb931b37f5..31dfe6ec87b 100644
--- a/extensions/jui/DatePickerRegionalAsset.php
+++ b/extensions/jui/DatePickerRegionalAsset.php
@@ -15,11 +15,11 @@
*/
class DatePickerRegionalAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.datepicker-i18n.js',
- ];
- public $depends = [
- 'yii\jui\DatePickerAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.datepicker-i18n.js',
+ ];
+ public $depends = [
+ 'yii\jui\DatePickerAsset',
+ ];
}
diff --git a/extensions/jui/Dialog.php b/extensions/jui/Dialog.php
index a5cbaf2b691..f7e49a8f6aa 100644
--- a/extensions/jui/Dialog.php
+++ b/extensions/jui/Dialog.php
@@ -32,21 +32,21 @@
*/
class Dialog extends Widget
{
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- echo Html::beginTag('div', $this->options) . "\n";
- }
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ echo Html::beginTag('div', $this->options) . "\n";
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::endTag('div') . "\n";
- $this->registerWidget('dialog', DialogAsset::className());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::endTag('div') . "\n";
+ $this->registerWidget('dialog', DialogAsset::className());
+ }
}
diff --git a/extensions/jui/DialogAsset.php b/extensions/jui/DialogAsset.php
index da6a32cfe6c..f14a096f7ed 100644
--- a/extensions/jui/DialogAsset.php
+++ b/extensions/jui/DialogAsset.php
@@ -15,14 +15,14 @@
*/
class DialogAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.dialog.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- 'yii\jui\ButtonAsset',
- 'yii\jui\DraggableAsset',
- 'yii\jui\ResizableAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.dialog.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ 'yii\jui\ButtonAsset',
+ 'yii\jui\DraggableAsset',
+ 'yii\jui\ResizableAsset',
+ ];
}
diff --git a/extensions/jui/Draggable.php b/extensions/jui/Draggable.php
index 02e4973b88a..f54971aad85 100644
--- a/extensions/jui/Draggable.php
+++ b/extensions/jui/Draggable.php
@@ -30,21 +30,21 @@
*/
class Draggable extends Widget
{
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- echo Html::beginTag('div', $this->options) . "\n";
- }
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ echo Html::beginTag('div', $this->options) . "\n";
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::endTag('div') . "\n";
- $this->registerWidget('draggable', DraggableAsset::className());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::endTag('div') . "\n";
+ $this->registerWidget('draggable', DraggableAsset::className());
+ }
}
diff --git a/extensions/jui/DraggableAsset.php b/extensions/jui/DraggableAsset.php
index 152ea6e14dd..742ab04f72b 100644
--- a/extensions/jui/DraggableAsset.php
+++ b/extensions/jui/DraggableAsset.php
@@ -15,11 +15,11 @@
*/
class DraggableAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.draggable.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.draggable.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/Droppable.php b/extensions/jui/Droppable.php
index 530e736f837..d0c247d8467 100644
--- a/extensions/jui/Droppable.php
+++ b/extensions/jui/Droppable.php
@@ -30,21 +30,21 @@
*/
class Droppable extends Widget
{
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- echo Html::beginTag('div', $this->options) . "\n";
- }
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ echo Html::beginTag('div', $this->options) . "\n";
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::endTag('div') . "\n";
- $this->registerWidget('droppable', DroppableAsset::className());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::endTag('div') . "\n";
+ $this->registerWidget('droppable', DroppableAsset::className());
+ }
}
diff --git a/extensions/jui/DroppableAsset.php b/extensions/jui/DroppableAsset.php
index ae7afa865dd..377f9dac003 100644
--- a/extensions/jui/DroppableAsset.php
+++ b/extensions/jui/DroppableAsset.php
@@ -15,11 +15,11 @@
*/
class DroppableAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.droppable.js',
- ];
- public $depends = [
- 'yii\jui\DraggableAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.droppable.js',
+ ];
+ public $depends = [
+ 'yii\jui\DraggableAsset',
+ ];
}
diff --git a/extensions/jui/EffectAsset.php b/extensions/jui/EffectAsset.php
index d28347c9098..b8cade08e19 100644
--- a/extensions/jui/EffectAsset.php
+++ b/extensions/jui/EffectAsset.php
@@ -15,11 +15,11 @@
*/
class EffectAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.effect-all.js',
- ];
- public $depends = [
- 'yii\web\JqueryAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.effect-all.js',
+ ];
+ public $depends = [
+ 'yii\web\JqueryAsset',
+ ];
}
diff --git a/extensions/jui/InputWidget.php b/extensions/jui/InputWidget.php
index 77201bad5b9..b668ca47cc9 100644
--- a/extensions/jui/InputWidget.php
+++ b/extensions/jui/InputWidget.php
@@ -19,44 +19,43 @@
*/
class InputWidget extends Widget
{
- /**
- * @var Model the data model that this widget is associated with.
- */
- public $model;
- /**
- * @var string the model attribute that this widget is associated with.
- */
- public $attribute;
- /**
- * @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
- */
- public $name;
- /**
- * @var string the input value.
- */
- public $value;
+ /**
+ * @var Model the data model that this widget is associated with.
+ */
+ public $model;
+ /**
+ * @var string the model attribute that this widget is associated with.
+ */
+ public $attribute;
+ /**
+ * @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
+ */
+ public $name;
+ /**
+ * @var string the input value.
+ */
+ public $value;
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ if (!$this->hasModel() && $this->name === null) {
+ throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
+ }
+ if ($this->hasModel() && !isset($this->options['id'])) {
+ $this->options['id'] = Html::getInputId($this->model, $this->attribute);
+ }
+ parent::init();
+ }
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- if (!$this->hasModel() && $this->name === null) {
- throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
- }
- if ($this->hasModel() && !isset($this->options['id'])) {
- $this->options['id'] = Html::getInputId($this->model, $this->attribute);
- }
- parent::init();
- }
-
- /**
- * @return boolean whether this widget is associated with a data model.
- */
- protected function hasModel()
- {
- return $this->model instanceof Model && $this->attribute !== null;
- }
+ /**
+ * @return boolean whether this widget is associated with a data model.
+ */
+ protected function hasModel()
+ {
+ return $this->model instanceof Model && $this->attribute !== null;
+ }
}
diff --git a/extensions/jui/Menu.php b/extensions/jui/Menu.php
index bce215705e1..352634941ae 100644
--- a/extensions/jui/Menu.php
+++ b/extensions/jui/Menu.php
@@ -18,60 +18,59 @@
*/
class Menu extends \yii\widgets\Menu
{
- /**
- * @var array the options for the underlying jQuery UI widget.
- * Please refer to the corresponding jQuery UI widget Web page for possible options.
- * For example, [this page](http://api.jqueryui.com/accordion/) shows
- * how to use the "Accordion" widget and the supported options (e.g. "header").
- */
- public $clientOptions = [];
- /**
- * @var array the event handlers for the underlying jQuery UI widget.
- * Please refer to the corresponding jQuery UI widget Web page for possible events.
- * For example, [this page](http://api.jqueryui.com/accordion/) shows
- * how to use the "Accordion" widget and the supported events (e.g. "create").
- */
- public $clientEvents = [];
+ /**
+ * @var array the options for the underlying jQuery UI widget.
+ * Please refer to the corresponding jQuery UI widget Web page for possible options.
+ * For example, [this page](http://api.jqueryui.com/accordion/) shows
+ * how to use the "Accordion" widget and the supported options (e.g. "header").
+ */
+ public $clientOptions = [];
+ /**
+ * @var array the event handlers for the underlying jQuery UI widget.
+ * Please refer to the corresponding jQuery UI widget Web page for possible events.
+ * For example, [this page](http://api.jqueryui.com/accordion/) shows
+ * how to use the "Accordion" widget and the supported events (e.g. "create").
+ */
+ public $clientEvents = [];
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->getId();
+ }
+ }
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- parent::init();
- if (!isset($this->options['id'])) {
- $this->options['id'] = $this->getId();
- }
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ parent::run();
- /**
- * Renders the widget.
- */
- public function run()
- {
- parent::run();
+ $view = $this->getView();
+ MenuAsset::register($view);
+ /** @var \yii\web\AssetBundle $themeAsset */
+ $themeAsset = Widget::$theme;
+ $themeAsset::register($view);
- $view = $this->getView();
- MenuAsset::register($view);
- /** @var \yii\web\AssetBundle $themeAsset */
- $themeAsset = Widget::$theme;
- $themeAsset::register($view);
+ $id = $this->options['id'];
+ if ($this->clientOptions !== false) {
+ $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions);
+ $js = "jQuery('#$id').menu($options);";
+ $view->registerJs($js);
+ }
- $id = $this->options['id'];
- if ($this->clientOptions !== false) {
- $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions);
- $js = "jQuery('#$id').menu($options);";
- $view->registerJs($js);
- }
-
- if (!empty($this->clientEvents)) {
- $js = [];
- foreach ($this->clientEvents as $event => $handler) {
- $js[] = "jQuery('#$id').on('menu$event', $handler);";
- }
- $view->registerJs(implode("\n", $js));
- }
- }
+ if (!empty($this->clientEvents)) {
+ $js = [];
+ foreach ($this->clientEvents as $event => $handler) {
+ $js[] = "jQuery('#$id').on('menu$event', $handler);";
+ }
+ $view->registerJs(implode("\n", $js));
+ }
+ }
}
diff --git a/extensions/jui/MenuAsset.php b/extensions/jui/MenuAsset.php
index c4c66ec049f..fab40d7ed89 100644
--- a/extensions/jui/MenuAsset.php
+++ b/extensions/jui/MenuAsset.php
@@ -15,11 +15,11 @@
*/
class MenuAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.menu.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.menu.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/ProgressBar.php b/extensions/jui/ProgressBar.php
index 1c555f18f4c..b90f6563cae 100644
--- a/extensions/jui/ProgressBar.php
+++ b/extensions/jui/ProgressBar.php
@@ -40,21 +40,21 @@
*/
class ProgressBar extends Widget
{
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- echo Html::beginTag('div', $this->options) . "\n";
- }
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ echo Html::beginTag('div', $this->options) . "\n";
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::endTag('div') . "\n";
- $this->registerWidget('progressbar', ProgressBarAsset::className());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::endTag('div') . "\n";
+ $this->registerWidget('progressbar', ProgressBarAsset::className());
+ }
}
diff --git a/extensions/jui/ProgressBarAsset.php b/extensions/jui/ProgressBarAsset.php
index b7a5df29424..c607dc12683 100644
--- a/extensions/jui/ProgressBarAsset.php
+++ b/extensions/jui/ProgressBarAsset.php
@@ -15,11 +15,11 @@
*/
class ProgressBarAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.progressbar.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.progressbar.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/Resizable.php b/extensions/jui/Resizable.php
index bcff9a8bbdf..22e0ed59576 100644
--- a/extensions/jui/Resizable.php
+++ b/extensions/jui/Resizable.php
@@ -32,21 +32,21 @@
*/
class Resizable extends Widget
{
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
- echo Html::beginTag('div', $this->options) . "\n";
- }
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ echo Html::beginTag('div', $this->options) . "\n";
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo Html::endTag('div') . "\n";
- $this->registerWidget('resizable', ResizableAsset::className());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::endTag('div') . "\n";
+ $this->registerWidget('resizable', ResizableAsset::className());
+ }
}
diff --git a/extensions/jui/ResizableAsset.php b/extensions/jui/ResizableAsset.php
index 9097a8f63dc..a4e1d83417a 100644
--- a/extensions/jui/ResizableAsset.php
+++ b/extensions/jui/ResizableAsset.php
@@ -15,11 +15,11 @@
*/
class ResizableAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.resizable.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.resizable.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/Selectable.php b/extensions/jui/Selectable.php
index 3055c03cabf..6af3f5815cb 100644
--- a/extensions/jui/Selectable.php
+++ b/extensions/jui/Selectable.php
@@ -48,73 +48,73 @@
*/
class Selectable extends Widget
{
- /**
- * @var array the HTML attributes for the widget container tag. The following special options are recognized:
- *
- * - tag: string, defaults to "ul", the tag name of the container tag of this widget.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array list of selectable items. Each item can be a string representing the item content
- * or an array of the following structure:
- *
- * ~~~
- * [
- * 'content' => 'item content',
- * // the HTML attributes of the item container tag. This will overwrite "itemOptions".
- * 'options' => [],
- * ]
- * ~~~
- */
- public $items = [];
- /**
- * @var array list of HTML attributes for the item container tags. This will be overwritten
- * by the "options" set in individual [[items]]. The following special options are recognized:
- *
- * - tag: string, defaults to "li", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $itemOptions = [];
+ /**
+ * @var array the HTML attributes for the widget container tag. The following special options are recognized:
+ *
+ * - tag: string, defaults to "ul", the tag name of the container tag of this widget.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array list of selectable items. Each item can be a string representing the item content
+ * or an array of the following structure:
+ *
+ * ~~~
+ * [
+ * 'content' => 'item content',
+ * // the HTML attributes of the item container tag. This will overwrite "itemOptions".
+ * 'options' => [],
+ * ]
+ * ~~~
+ */
+ public $items = [];
+ /**
+ * @var array list of HTML attributes for the item container tags. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "li", the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $itemOptions = [];
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ $options = $this->options;
+ $tag = ArrayHelper::remove($options, 'tag', 'ul');
+ echo Html::beginTag($tag, $options) . "\n";
+ echo $this->renderItems() . "\n";
+ echo Html::endTag($tag) . "\n";
+ $this->registerWidget('selectable', SelectableAsset::className());
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- $options = $this->options;
- $tag = ArrayHelper::remove($options, 'tag', 'ul');
- echo Html::beginTag($tag, $options) . "\n";
- echo $this->renderItems() . "\n";
- echo Html::endTag($tag) . "\n";
- $this->registerWidget('selectable', SelectableAsset::className());
- }
+ /**
+ * Renders selectable items as specified on [[items]].
+ * @return string the rendering result.
+ * @throws InvalidConfigException.
+ */
+ public function renderItems()
+ {
+ $items = [];
+ foreach ($this->items as $item) {
+ $options = $this->itemOptions;
+ $tag = ArrayHelper::remove($options, 'tag', 'li');
+ if (is_array($item)) {
+ if (!isset($item['content'])) {
+ throw new InvalidConfigException("The 'content' option is required.");
+ }
+ $options = array_merge($options, ArrayHelper::getValue($item, 'options', []));
+ $tag = ArrayHelper::remove($options, 'tag', $tag);
+ $items[] = Html::tag($tag, $item['content'], $options);
+ } else {
+ $items[] = Html::tag($tag, $item, $options);
+ }
+ }
- /**
- * Renders selectable items as specified on [[items]].
- * @return string the rendering result.
- * @throws InvalidConfigException.
- */
- public function renderItems()
- {
- $items = [];
- foreach ($this->items as $item) {
- $options = $this->itemOptions;
- $tag = ArrayHelper::remove($options, 'tag', 'li');
- if (is_array($item)) {
- if (!isset($item['content'])) {
- throw new InvalidConfigException("The 'content' option is required.");
- }
- $options = array_merge($options, ArrayHelper::getValue($item, 'options', []));
- $tag = ArrayHelper::remove($options, 'tag', $tag);
- $items[] = Html::tag($tag, $item['content'], $options);
- } else {
- $items[] = Html::tag($tag, $item, $options);
- }
- }
- return implode("\n", $items);
- }
+ return implode("\n", $items);
+ }
}
diff --git a/extensions/jui/SelectableAsset.php b/extensions/jui/SelectableAsset.php
index b794756357a..073be5207d5 100644
--- a/extensions/jui/SelectableAsset.php
+++ b/extensions/jui/SelectableAsset.php
@@ -15,11 +15,11 @@
*/
class SelectableAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.selectable.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.selectable.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/Slider.php b/extensions/jui/Slider.php
index 5d80894937b..64c79e5505a 100644
--- a/extensions/jui/Slider.php
+++ b/extensions/jui/Slider.php
@@ -26,23 +26,23 @@
*/
class Slider extends Widget
{
- /**
- * @inheritDoc
- */
- protected $clientEventMap = [
- 'change' => 'slidechange',
- 'create' => 'slidecreate',
- 'slide' => 'slide',
- 'start' => 'slidestart',
- 'stop' => 'slidestop',
- ];
+ /**
+ * @inheritDoc
+ */
+ protected $clientEventMap = [
+ 'change' => 'slidechange',
+ 'create' => 'slidecreate',
+ 'slide' => 'slide',
+ 'start' => 'slidestart',
+ 'stop' => 'slidestop',
+ ];
- /**
- * Executes the widget.
- */
- public function run()
- {
- echo Html::tag('div', '', $this->options);
- $this->registerWidget('slider', SliderAsset::className());
- }
+ /**
+ * Executes the widget.
+ */
+ public function run()
+ {
+ echo Html::tag('div', '', $this->options);
+ $this->registerWidget('slider', SliderAsset::className());
+ }
}
diff --git a/extensions/jui/SliderAsset.php b/extensions/jui/SliderAsset.php
index fcba7766d6f..96794a515c1 100644
--- a/extensions/jui/SliderAsset.php
+++ b/extensions/jui/SliderAsset.php
@@ -15,11 +15,11 @@
*/
class SliderAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.slider.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.slider.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/SliderInput.php b/extensions/jui/SliderInput.php
index b2383369f40..967a91ea46e 100644
--- a/extensions/jui/SliderInput.php
+++ b/extensions/jui/SliderInput.php
@@ -43,54 +43,54 @@
*/
class SliderInput extends InputWidget
{
- /**
- * @inheritDoc
- */
- protected $clientEventMap = [
- 'change' => 'slidechange',
- 'create' => 'slidecreate',
- 'slide' => 'slide',
- 'start' => 'slidestart',
- 'stop' => 'slidestop',
- ];
- /**
- * @var array the HTML attributes for the container tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $containerOptions = [];
+ /**
+ * @inheritDoc
+ */
+ protected $clientEventMap = [
+ 'change' => 'slidechange',
+ 'create' => 'slidecreate',
+ 'slide' => 'slide',
+ 'start' => 'slidestart',
+ 'stop' => 'slidestop',
+ ];
+ /**
+ * @var array the HTML attributes for the container tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $containerOptions = [];
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if (!isset($this->containerOptions['id'])) {
- $this->containerOptions['id'] = $this->options['id'] . '-container';
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->containerOptions['id'])) {
+ $this->containerOptions['id'] = $this->options['id'] . '-container';
+ }
+ }
- /**
- * Executes the widget.
- */
- public function run()
- {
- echo Html::tag('div', '', $this->containerOptions);
+ /**
+ * Executes the widget.
+ */
+ public function run()
+ {
+ echo Html::tag('div', '', $this->containerOptions);
- if ($this->hasModel()) {
- echo Html::activeHiddenInput($this->model, $this->attribute, $this->options);
- $this->clientOptions['value'] = $this->model{$this->attribute};
- } else {
- echo Html::hiddenInput($this->name, $this->value, $this->options);
- $this->clientOptions['value'] = $this->value;
- }
+ if ($this->hasModel()) {
+ echo Html::activeHiddenInput($this->model, $this->attribute, $this->options);
+ $this->clientOptions['value'] = $this->model{$this->attribute};
+ } else {
+ echo Html::hiddenInput($this->name, $this->value, $this->options);
+ $this->clientOptions['value'] = $this->value;
+ }
- if (!isset($this->clientEvents['slide'])) {
- $this->clientEvents['slide'] = 'function(event, ui) {
- $("#' . $this->options['id'] . '").val(ui.value);
- }';
- }
+ if (!isset($this->clientEvents['slide'])) {
+ $this->clientEvents['slide'] = 'function (event, ui) {
+ $("#' . $this->options['id'] . '").val(ui.value);
+ }';
+ }
- $this->registerWidget('slider', SliderAsset::className(), $this->containerOptions['id']);
- }
+ $this->registerWidget('slider', SliderAsset::className(), $this->containerOptions['id']);
+ }
}
diff --git a/extensions/jui/Sortable.php b/extensions/jui/Sortable.php
index 19c93a9778a..a87cdf63475 100644
--- a/extensions/jui/Sortable.php
+++ b/extensions/jui/Sortable.php
@@ -38,92 +38,92 @@
*/
class Sortable extends Widget
{
- /**
- * @var array the HTML attributes for the widget container tag. The following special options are recognized:
- *
- * - tag: string, defaults to "ul", the tag name of the container tag of this widget.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array list of sortable items. Each item can be a string representing the item content
- * or an array of the following structure:
- *
- * ~~~
- * [
- * 'content' => 'item content',
- * // the HTML attributes of the item container tag. This will overwrite "itemOptions".
- * 'options' => [],
- * ]
- * ~~~
- */
- public $items = [];
- /**
- * @var array list of HTML attributes for the item container tags. This will be overwritten
- * by the "options" set in individual [[items]]. The following special options are recognized:
- *
- * - tag: string, defaults to "li", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $itemOptions = [];
+ /**
+ * @var array the HTML attributes for the widget container tag. The following special options are recognized:
+ *
+ * - tag: string, defaults to "ul", the tag name of the container tag of this widget.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array list of sortable items. Each item can be a string representing the item content
+ * or an array of the following structure:
+ *
+ * ~~~
+ * [
+ * 'content' => 'item content',
+ * // the HTML attributes of the item container tag. This will overwrite "itemOptions".
+ * 'options' => [],
+ * ]
+ * ~~~
+ */
+ public $items = [];
+ /**
+ * @var array list of HTML attributes for the item container tags. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "li", the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $itemOptions = [];
- /**
- * @inheritDoc
- */
- protected $clientEventMap = [
- 'activate' => 'sortactivate',
- 'beforeStop' => 'sortbeforestop',
- 'change' => 'sortchange',
- 'create' => 'sortcreate',
- 'deactivate' => 'sortdeactivate',
- 'out' => 'sortout',
- 'over' => 'sortover',
- 'receive' => 'sortreceive',
- 'remove' => 'sortremove',
- 'sort' => 'sort',
- 'start' => 'sortstart',
- 'stop' => 'sortstop',
- 'update' => 'sortupdate',
- ];
+ /**
+ * @inheritDoc
+ */
+ protected $clientEventMap = [
+ 'activate' => 'sortactivate',
+ 'beforeStop' => 'sortbeforestop',
+ 'change' => 'sortchange',
+ 'create' => 'sortcreate',
+ 'deactivate' => 'sortdeactivate',
+ 'out' => 'sortout',
+ 'over' => 'sortover',
+ 'receive' => 'sortreceive',
+ 'remove' => 'sortremove',
+ 'sort' => 'sort',
+ 'start' => 'sortstart',
+ 'stop' => 'sortstop',
+ 'update' => 'sortupdate',
+ ];
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ $options = $this->options;
+ $tag = ArrayHelper::remove($options, 'tag', 'ul');
+ echo Html::beginTag($tag, $options) . "\n";
+ echo $this->renderItems() . "\n";
+ echo Html::endTag($tag) . "\n";
+ $this->registerWidget('sortable', SortableAsset::className());
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- $options = $this->options;
- $tag = ArrayHelper::remove($options, 'tag', 'ul');
- echo Html::beginTag($tag, $options) . "\n";
- echo $this->renderItems() . "\n";
- echo Html::endTag($tag) . "\n";
- $this->registerWidget('sortable', SortableAsset::className());
- }
+ /**
+ * Renders sortable items as specified on [[items]].
+ * @return string the rendering result.
+ * @throws InvalidConfigException.
+ */
+ public function renderItems()
+ {
+ $items = [];
+ foreach ($this->items as $item) {
+ $options = $this->itemOptions;
+ $tag = ArrayHelper::remove($options, 'tag', 'li');
+ if (is_array($item)) {
+ if (!isset($item['content'])) {
+ throw new InvalidConfigException("The 'content' option is required.");
+ }
+ $options = array_merge($options, ArrayHelper::getValue($item, 'options', []));
+ $tag = ArrayHelper::remove($options, 'tag', $tag);
+ $items[] = Html::tag($tag, $item['content'], $options);
+ } else {
+ $items[] = Html::tag($tag, $item, $options);
+ }
+ }
- /**
- * Renders sortable items as specified on [[items]].
- * @return string the rendering result.
- * @throws InvalidConfigException.
- */
- public function renderItems()
- {
- $items = [];
- foreach ($this->items as $item) {
- $options = $this->itemOptions;
- $tag = ArrayHelper::remove($options, 'tag', 'li');
- if (is_array($item)) {
- if (!isset($item['content'])) {
- throw new InvalidConfigException("The 'content' option is required.");
- }
- $options = array_merge($options, ArrayHelper::getValue($item, 'options', []));
- $tag = ArrayHelper::remove($options, 'tag', $tag);
- $items[] = Html::tag($tag, $item['content'], $options);
- } else {
- $items[] = Html::tag($tag, $item, $options);
- }
- }
- return implode("\n", $items);
- }
+ return implode("\n", $items);
+ }
}
diff --git a/extensions/jui/SortableAsset.php b/extensions/jui/SortableAsset.php
index 5f31f73447f..f7de366f2bb 100644
--- a/extensions/jui/SortableAsset.php
+++ b/extensions/jui/SortableAsset.php
@@ -15,11 +15,11 @@
*/
class SortableAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.sortable.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.sortable.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ ];
}
diff --git a/extensions/jui/Spinner.php b/extensions/jui/Spinner.php
index c57c59aa59f..1222515d185 100644
--- a/extensions/jui/Spinner.php
+++ b/extensions/jui/Spinner.php
@@ -37,32 +37,32 @@
*/
class Spinner extends InputWidget
{
- /**
- * @inheritDoc
- */
- protected $clientEventMap = [
- 'spin' => 'spin',
- ];
+ /**
+ * @inheritDoc
+ */
+ protected $clientEventMap = [
+ 'spin' => 'spin',
+ ];
- /**
- * Renders the widget.
- */
- public function run()
- {
- echo $this->renderWidget();
- $this->registerWidget('spinner', SpinnerAsset::className());
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderWidget();
+ $this->registerWidget('spinner', SpinnerAsset::className());
+ }
- /**
- * Renders the Spinner widget.
- * @return string the rendering result.
- */
- public function renderWidget()
- {
- if ($this->hasModel()) {
- return Html::activeTextInput($this->model, $this->attribute, $this->options);
- } else {
- return Html::textInput($this->name, $this->value, $this->options);
- }
- }
+ /**
+ * Renders the Spinner widget.
+ * @return string the rendering result.
+ */
+ public function renderWidget()
+ {
+ if ($this->hasModel()) {
+ return Html::activeTextInput($this->model, $this->attribute, $this->options);
+ } else {
+ return Html::textInput($this->name, $this->value, $this->options);
+ }
+ }
}
diff --git a/extensions/jui/SpinnerAsset.php b/extensions/jui/SpinnerAsset.php
index 04bc698bea6..8c36ef4c70b 100644
--- a/extensions/jui/SpinnerAsset.php
+++ b/extensions/jui/SpinnerAsset.php
@@ -15,12 +15,12 @@
*/
class SpinnerAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.spinner.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- 'yii\jui\ButtonAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.spinner.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ 'yii\jui\ButtonAsset',
+ ];
}
diff --git a/extensions/jui/Tabs.php b/extensions/jui/Tabs.php
index 6d3b0f6b30a..4bff01e5c5d 100644
--- a/extensions/jui/Tabs.php
+++ b/extensions/jui/Tabs.php
@@ -53,99 +53,99 @@
*/
class Tabs extends Widget
{
- /**
- * @var array the HTML attributes for the widget container tag. The following special options are recognized:
- *
- * - tag: string, defaults to "div", the tag name of the container tag of this widget.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array list of tab items. Each item can be an array of the following structure:
- *
- * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label
- * will be HTML-encoded.
- * - content: string, the content to show when corresponding tab is clicked. Can be omitted if url is specified.
- * - url: mixed, mixed, optional, the url to load tab contents via AJAX. It is required if no content is specified.
- * - template: string, optional, the header link template to render the header link. If none specified
- * [[linkTemplate]] will be used instead.
- * - options: array, optional, the HTML attributes of the header.
- * - headerOptions: array, optional, the HTML attributes for the header container tag.
- */
- public $items = [];
- /**
- * @var array list of HTML attributes for the item container tags. This will be overwritten
- * by the "options" set in individual [[items]]. The following special options are recognized:
- *
- * - tag: string, defaults to "div", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $itemOptions = [];
- /**
- * @var array list of HTML attributes for the header container tags. This will be overwritten
- * by the "headerOptions" set in individual [[items]].
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $headerOptions = [];
- /**
- * @var string the default header template to render the link.
- */
- public $linkTemplate = '{label}';
- /**
- * @var boolean whether the labels for header items should be HTML-encoded.
- */
- public $encodeLabels = true;
+ /**
+ * @var array the HTML attributes for the widget container tag. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div", the tag name of the container tag of this widget.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array list of tab items. Each item can be an array of the following structure:
+ *
+ * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label
+ * will be HTML-encoded.
+ * - content: string, the content to show when corresponding tab is clicked. Can be omitted if url is specified.
+ * - url: mixed, mixed, optional, the url to load tab contents via AJAX. It is required if no content is specified.
+ * - template: string, optional, the header link template to render the header link. If none specified
+ * [[linkTemplate]] will be used instead.
+ * - options: array, optional, the HTML attributes of the header.
+ * - headerOptions: array, optional, the HTML attributes for the header container tag.
+ */
+ public $items = [];
+ /**
+ * @var array list of HTML attributes for the item container tags. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div", the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $itemOptions = [];
+ /**
+ * @var array list of HTML attributes for the header container tags. This will be overwritten
+ * by the "headerOptions" set in individual [[items]].
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $headerOptions = [];
+ /**
+ * @var string the default header template to render the link.
+ */
+ public $linkTemplate = '{label}';
+ /**
+ * @var boolean whether the labels for header items should be HTML-encoded.
+ */
+ public $encodeLabels = true;
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ $options = $this->options;
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ echo Html::beginTag($tag, $options) . "\n";
+ echo $this->renderItems() . "\n";
+ echo Html::endTag($tag) . "\n";
+ $this->registerWidget('tabs', TabsAsset::className());
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- $options = $this->options;
- $tag = ArrayHelper::remove($options, 'tag', 'div');
- echo Html::beginTag($tag, $options) . "\n";
- echo $this->renderItems() . "\n";
- echo Html::endTag($tag) . "\n";
- $this->registerWidget('tabs', TabsAsset::className());
- }
+ /**
+ * Renders tab items as specified on [[items]].
+ * @return string the rendering result.
+ * @throws InvalidConfigException.
+ */
+ protected function renderItems()
+ {
+ $headers = [];
+ $items = [];
+ foreach ($this->items as $n => $item) {
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ if (isset($item['url'])) {
+ $url = Url::to($item['url']);
+ } else {
+ if (!isset($item['content'])) {
+ throw new InvalidConfigException("The 'content' or 'url' option is required.");
+ }
+ $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ if (!isset($options['id'])) {
+ $options['id'] = $this->options['id'] . '-tab' . $n;
+ }
+ $url = '#' . $options['id'];
+ $items[] = Html::tag($tag, $item['content'], $options);
+ }
+ $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
+ $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate);
+ $headers[] = Html::tag('li', strtr($template, [
+ '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'],
+ '{url}' => $url,
+ ]), $headerOptions);
+ }
- /**
- * Renders tab items as specified on [[items]].
- * @return string the rendering result.
- * @throws InvalidConfigException.
- */
- protected function renderItems()
- {
- $headers = [];
- $items = [];
- foreach ($this->items as $n => $item) {
- if (!isset($item['label'])) {
- throw new InvalidConfigException("The 'label' option is required.");
- }
- if (isset($item['url'])) {
- $url = Url::to($item['url']);
- } else {
- if (!isset($item['content'])) {
- throw new InvalidConfigException("The 'content' or 'url' option is required.");
- }
- $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
- $tag = ArrayHelper::remove($options, 'tag', 'div');
- if (!isset($options['id'])) {
- $options['id'] = $this->options['id'] . '-tab' . $n;
- }
- $url = '#' . $options['id'];
- $items[] = Html::tag($tag, $item['content'], $options);
- }
- $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
- $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate);
- $headers[] = Html::tag('li', strtr($template, [
- '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'],
- '{url}' => $url,
- ]), $headerOptions);
- }
- return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items);
- }
+ return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items);
+ }
}
diff --git a/extensions/jui/TabsAsset.php b/extensions/jui/TabsAsset.php
index 49b3bad10f7..0ae7f87bbe7 100644
--- a/extensions/jui/TabsAsset.php
+++ b/extensions/jui/TabsAsset.php
@@ -15,12 +15,12 @@
*/
class TabsAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.tabs.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- 'yii\jui\EffectAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.tabs.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ 'yii\jui\EffectAsset',
+ ];
}
diff --git a/extensions/jui/ThemeAsset.php b/extensions/jui/ThemeAsset.php
index 8c7e78e0388..bfb3abf81a6 100644
--- a/extensions/jui/ThemeAsset.php
+++ b/extensions/jui/ThemeAsset.php
@@ -15,8 +15,8 @@
*/
class ThemeAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $css = [
- 'theme/jquery.ui.css',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $css = [
+ 'theme/jquery.ui.css',
+ ];
}
diff --git a/extensions/jui/TooltipAsset.php b/extensions/jui/TooltipAsset.php
index f0139a1c0ce..6e2778a4e99 100644
--- a/extensions/jui/TooltipAsset.php
+++ b/extensions/jui/TooltipAsset.php
@@ -15,12 +15,12 @@
*/
class TooltipAsset extends AssetBundle
{
- public $sourcePath = '@yii/jui/assets';
- public $js = [
- 'jquery.ui.tooltip.js',
- ];
- public $depends = [
- 'yii\jui\CoreAsset',
- 'yii\jui\EffectAsset',
- ];
+ public $sourcePath = '@yii/jui/assets';
+ public $js = [
+ 'jquery.ui.tooltip.js',
+ ];
+ public $depends = [
+ 'yii\jui\CoreAsset',
+ 'yii\jui\EffectAsset',
+ ];
}
diff --git a/extensions/jui/Widget.php b/extensions/jui/Widget.php
index 6eb20f8806f..ff2c8fe5041 100644
--- a/extensions/jui/Widget.php
+++ b/extensions/jui/Widget.php
@@ -17,121 +17,120 @@
*/
class Widget extends \yii\base\Widget
{
- /**
- * @var string the jQuery UI theme. This refers to an asset bundle class
- * representing the JUI theme. The default theme is the official "Smoothness" theme.
- */
- public static $theme = 'yii\jui\ThemeAsset';
- /**
- * @var array the HTML attributes for the widget container tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array the options for the underlying jQuery UI widget.
- * Please refer to the corresponding jQuery UI widget Web page for possible options.
- * For example, [this page](http://api.jqueryui.com/accordion/) shows
- * how to use the "Accordion" widget and the supported options (e.g. "header").
- */
- public $clientOptions = [];
- /**
- * @var array the event handlers for the underlying jQuery UI widget.
- * Please refer to the corresponding jQuery UI widget Web page for possible events.
- * For example, [this page](http://api.jqueryui.com/accordion/) shows
- * how to use the "Accordion" widget and the supported events (e.g. "create").
- * Keys are the event names and values are javascript code that is passed to the `.on()` function
- * as the event handler.
- *
- * For example you could write the following in your widget configuration:
- *
- * ```php
- * 'clientEvents' => [
- * 'change' => 'function() { alert('event "change" occured.'); }'
- * ],
- * ```
- */
- public $clientEvents = [];
+ /**
+ * @var string the jQuery UI theme. This refers to an asset bundle class
+ * representing the JUI theme. The default theme is the official "Smoothness" theme.
+ */
+ public static $theme = 'yii\jui\ThemeAsset';
+ /**
+ * @var array the HTML attributes for the widget container tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array the options for the underlying jQuery UI widget.
+ * Please refer to the corresponding jQuery UI widget Web page for possible options.
+ * For example, [this page](http://api.jqueryui.com/accordion/) shows
+ * how to use the "Accordion" widget and the supported options (e.g. "header").
+ */
+ public $clientOptions = [];
+ /**
+ * @var array the event handlers for the underlying jQuery UI widget.
+ * Please refer to the corresponding jQuery UI widget Web page for possible events.
+ * For example, [this page](http://api.jqueryui.com/accordion/) shows
+ * how to use the "Accordion" widget and the supported events (e.g. "create").
+ * Keys are the event names and values are javascript code that is passed to the `.on()` function
+ * as the event handler.
+ *
+ * For example you could write the following in your widget configuration:
+ *
+ * ```php
+ * 'clientEvents' => [
+ * 'change' => 'function () { alert('event "change" occured.'); }'
+ * ],
+ * ```
+ */
+ public $clientEvents = [];
- /**
- * @var array event names mapped to what should be specified in .on(
- * If empty, it is assumed that event passed to clientEvents is prefixed with widget name.
- */
- protected $clientEventMap = [];
+ /**
+ * @var array event names mapped to what should be specified in .on(
+ * If empty, it is assumed that event passed to clientEvents is prefixed with widget name.
+ */
+ protected $clientEventMap = [];
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->getId();
+ }
+ }
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- parent::init();
- if (!isset($this->options['id'])) {
- $this->options['id'] = $this->getId();
- }
- }
+ /**
+ * Registers a specific jQuery UI widget assets
+ * @param string $assetBundle the asset bundle for the widget
+ */
+ protected function registerAssets($assetBundle)
+ {
+ /** @var \yii\web\AssetBundle $assetBundle */
+ $assetBundle::register($this->getView());
+ /** @var \yii\web\AssetBundle $themeAsset */
+ $themeAsset = static::$theme;
+ $themeAsset::register($this->getView());
+ }
- /**
- * Registers a specific jQuery UI widget assets
- * @param string $assetBundle the asset bundle for the widget
- */
- protected function registerAssets($assetBundle)
- {
- /** @var \yii\web\AssetBundle $assetBundle */
- $assetBundle::register($this->getView());
- /** @var \yii\web\AssetBundle $themeAsset */
- $themeAsset = static::$theme;
- $themeAsset::register($this->getView());
- }
+ /**
+ * Registers a specific jQuery UI widget options
+ * @param string $name the name of the jQuery UI widget
+ * @param string $id the ID of the widget
+ */
+ protected function registerClientOptions($name, $id)
+ {
+ if ($this->clientOptions !== false) {
+ $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions);
+ $js = "jQuery('#$id').$name($options);";
+ $this->getView()->registerJs($js);
+ }
+ }
- /**
- * Registers a specific jQuery UI widget options
- * @param string $name the name of the jQuery UI widget
- * @param string $id the ID of the widget
- */
- protected function registerClientOptions($name, $id)
- {
- if ($this->clientOptions !== false) {
- $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions);
- $js = "jQuery('#$id').$name($options);";
- $this->getView()->registerJs($js);
- }
- }
+ /**
+ * Registers a specific jQuery UI widget events
+ * @param string $name the name of the jQuery UI widget
+ * @param string $id the ID of the widget
+ */
+ protected function registerClientEvents($name, $id)
+ {
+ if (!empty($this->clientEvents)) {
+ $js = [];
+ foreach ($this->clientEvents as $event => $handler) {
+ if (isset($this->clientEventMap[$event])) {
+ $eventName = $this->clientEventMap[$event];
+ } else {
+ $eventName = strtolower($name . $event);
+ }
+ $js[] = "jQuery('#$id').on('$eventName', $handler);";
+ }
+ $this->getView()->registerJs(implode("\n", $js));
+ }
+ }
- /**
- * Registers a specific jQuery UI widget events
- * @param string $name the name of the jQuery UI widget
- * @param string $id the ID of the widget
- */
- protected function registerClientEvents($name, $id)
- {
- if (!empty($this->clientEvents)) {
- $js = [];
- foreach ($this->clientEvents as $event => $handler) {
- if (isset($this->clientEventMap[$event])) {
- $eventName = $this->clientEventMap[$event];
- } else {
- $eventName = strtolower($name . $event);
- }
- $js[] = "jQuery('#$id').on('$eventName', $handler);";
- }
- $this->getView()->registerJs(implode("\n", $js));
- }
- }
-
- /**
- * Registers a specific jQuery UI widget asset bundle, initializes it with client options and registers related events
- * @param string $name the name of the jQuery UI widget
- * @param string $assetBundle the asset bundle for the widget
- * @param string $id the ID of the widget. If null, it will use the `id` value of [[options]].
- */
- protected function registerWidget($name, $assetBundle, $id = null)
- {
- if ($id === null) {
- $id = $this->options['id'];
- }
- $this->registerAssets($assetBundle);
- $this->registerClientOptions($name, $id);
- $this->registerClientEvents($name, $id);
- }
+ /**
+ * Registers a specific jQuery UI widget asset bundle, initializes it with client options and registers related events
+ * @param string $name the name of the jQuery UI widget
+ * @param string $assetBundle the asset bundle for the widget
+ * @param string $id the ID of the widget. If null, it will use the `id` value of [[options]].
+ */
+ protected function registerWidget($name, $assetBundle, $id = null)
+ {
+ if ($id === null) {
+ $id = $this->options['id'];
+ }
+ $this->registerAssets($assetBundle);
+ $this->registerClientOptions($name, $id);
+ $this->registerClientEvents($name, $id);
+ }
}
diff --git a/extensions/mongodb/ActiveFixture.php b/extensions/mongodb/ActiveFixture.php
index 2acc5f4c207..3d3082aeeca 100644
--- a/extensions/mongodb/ActiveFixture.php
+++ b/extensions/mongodb/ActiveFixture.php
@@ -28,100 +28,101 @@
*/
class ActiveFixture extends BaseActiveFixture
{
- /**
- * @var Connection|string the DB connection object or the application component ID of the DB connection.
- */
- public $db = 'mongodb';
- /**
- * @var string|array the collection name that this fixture is about. If this property is not set,
- * the table name will be determined via [[modelClass]].
- * @see Connection::getCollection()
- */
- public $collectionName;
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ */
+ public $db = 'mongodb';
+ /**
+ * @var string|array the collection name that this fixture is about. If this property is not set,
+ * the table name will be determined via [[modelClass]].
+ * @see Connection::getCollection()
+ */
+ public $collectionName;
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if (!isset($this->modelClass) && !isset($this->collectionName)) {
- throw new InvalidConfigException('Either "modelClass" or "collectionName" must be set.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->modelClass) && !isset($this->collectionName)) {
+ throw new InvalidConfigException('Either "modelClass" or "collectionName" must be set.');
+ }
+ }
- /**
- * Loads the fixture data.
- * Data will be batch inserted into the given collection.
- */
- public function load()
- {
- parent::load();
+ /**
+ * Loads the fixture data.
+ * Data will be batch inserted into the given collection.
+ */
+ public function load()
+ {
+ parent::load();
- $data = $this->getData();
- $this->getCollection()->batchInsert($data);
- foreach ($data as $alias => $row) {
- $this->data[$alias] = $row;
- }
- }
+ $data = $this->getData();
+ $this->getCollection()->batchInsert($data);
+ foreach ($data as $alias => $row) {
+ $this->data[$alias] = $row;
+ }
+ }
- /**
- * Unloads the fixture.
- *
- * The default implementation will clean up the colection by calling [[resetCollection()]].
- */
- public function unload()
- {
- $this->resetCollection();
- parent::unload();
- }
+ /**
+ * 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());
- }
+ protected function getCollection()
+ {
+ return $this->db->getCollection($this->getCollectionName());
+ }
- protected function getCollectionName()
- {
- if ($this->collectionName) {
- return $this->collectionName;
- } else {
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- return $modelClass::collectionName();
- }
- }
+ protected function getCollectionName()
+ {
+ if ($this->collectionName) {
+ return $this->collectionName;
+ } else {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
- /**
- * Returns the fixture data.
- *
- * This method is called by [[loadData()]] to get the needed fixture data.
- *
- * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
- * The file should return an array of data rows (column name => column value), each corresponding to a row in the table.
- *
- * If the data file does not exist, an empty array will be returned.
- *
- * @return array the data rows to be inserted into the collection.
- */
- protected function getData()
- {
- if ($this->dataFile === null) {
- $class = new \ReflectionClass($this);
- $dataFile = dirname($class->getFileName()) . '/data/' . $this->getCollectionName() . '.php';
- return is_file($dataFile) ? require($dataFile) : [];
- } else {
- return parent::getData();
- }
- }
+ return $modelClass::collectionName();
+ }
+ }
- /**
- * Removes all existing data from the specified collection and resets sequence number if any.
- * This method is called before populating fixture data into the collection associated with this fixture.
- */
- protected function resetCollection()
- {
- $this->getCollection()->remove();
- }
+ /**
+ * Returns the fixture data.
+ *
+ * This method is called by [[loadData()]] to get the needed fixture data.
+ *
+ * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
+ * The file should return an array of data rows (column name => column value), each corresponding to a row in the table.
+ *
+ * If the data file does not exist, an empty array will be returned.
+ *
+ * @return array the data rows to be inserted into the collection.
+ */
+ protected function getData()
+ {
+ if ($this->dataFile === null) {
+ $class = new \ReflectionClass($this);
+ $dataFile = dirname($class->getFileName()) . '/data/' . $this->getCollectionName() . '.php';
+
+ return is_file($dataFile) ? require($dataFile) : [];
+ } else {
+ return parent::getData();
+ }
+ }
+
+ /**
+ * Removes all existing data from the specified collection and resets sequence number if any.
+ * This method is called before populating fixture data into the collection associated with this fixture.
+ */
+ protected function resetCollection()
+ {
+ $this->getCollection()->remove();
+ }
}
diff --git a/extensions/mongodb/ActiveQuery.php b/extensions/mongodb/ActiveQuery.php
index fabea5f7429..95a242c9173 100644
--- a/extensions/mongodb/ActiveQuery.php
+++ b/extensions/mongodb/ActiveQuery.php
@@ -61,115 +61,119 @@
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
- use ActiveQueryTrait;
- use ActiveRelationTrait;
+ use ActiveQueryTrait;
+ use ActiveRelationTrait;
- /**
- * @inheritdoc
- */
- protected function buildCursor($db = null)
- {
- if ($this->primaryModel !== null) {
- // lazy loading
- if ($this->via instanceof self) {
- // via pivot collection
- $viaModels = $this->via->findPivotRows([$this->primaryModel]);
- $this->filterByModels($viaModels);
- } elseif (is_array($this->via)) {
- // via relation
- /** @var ActiveQuery $viaQuery */
- list($viaName, $viaQuery) = $this->via;
- if ($viaQuery->multiple) {
- $viaModels = $viaQuery->all();
- $this->primaryModel->populateRelation($viaName, $viaModels);
- } else {
- $model = $viaQuery->one();
- $this->primaryModel->populateRelation($viaName, $model);
- $viaModels = $model === null ? [] : [$model];
- }
- $this->filterByModels($viaModels);
- } else {
- $this->filterByModels([$this->primaryModel]);
- }
- }
- return parent::buildCursor($db);
- }
+ /**
+ * @inheritdoc
+ */
+ protected function buildCursor($db = null)
+ {
+ if ($this->primaryModel !== null) {
+ // lazy loading
+ if ($this->via instanceof self) {
+ // via pivot collection
+ $viaModels = $this->via->findPivotRows([$this->primaryModel]);
+ $this->filterByModels($viaModels);
+ } elseif (is_array($this->via)) {
+ // via relation
+ /** @var ActiveQuery $viaQuery */
+ list($viaName, $viaQuery) = $this->via;
+ if ($viaQuery->multiple) {
+ $viaModels = $viaQuery->all();
+ $this->primaryModel->populateRelation($viaName, $viaModels);
+ } else {
+ $model = $viaQuery->one();
+ $this->primaryModel->populateRelation($viaName, $model);
+ $viaModels = $model === null ? [] : [$model];
+ }
+ $this->filterByModels($viaModels);
+ } else {
+ $this->filterByModels([$this->primaryModel]);
+ }
+ }
- /**
- * Executes query and returns all results as an array.
- * @param Connection $db the Mongo connection used to execute the query.
- * If null, the Mongo connection returned by [[modelClass]] will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- $cursor = $this->buildCursor($db);
- $rows = $this->fetchRows($cursor);
- if (!empty($rows)) {
- $models = $this->createModels($rows);
- if (!empty($this->with)) {
- $this->findWith($this->with, $models);
- }
- if (!$this->asArray) {
- foreach ($models as $model) {
- $model->afterFind();
- }
- }
- return $models;
- } else {
- return [];
- }
- }
+ return parent::buildCursor($db);
+ }
- /**
- * Executes query and returns a single row of result.
- * @param Connection $db the Mongo connection used to execute the query.
- * If null, the Mongo connection returned by [[modelClass]] will be used.
- * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
- * the query result may be either an array or an ActiveRecord object. Null will be returned
- * if the query results in nothing.
- */
- public function one($db = null)
- {
- $row = parent::one($db);
- if ($row !== false) {
- if ($this->asArray) {
- $model = $row;
- } else {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $model = $class::instantiate($row);
- $class::populateRecord($model, $row);
- }
- if (!empty($this->with)) {
- $models = [$model];
- $this->findWith($this->with, $models);
- $model = $models[0];
- }
- if (!$this->asArray) {
- $model->afterFind();
- }
- return $model;
- } else {
- return null;
- }
- }
+ /**
+ * Executes query and returns all results as an array.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If null, the Mongo connection returned by [[modelClass]] will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $cursor = $this->buildCursor($db);
+ $rows = $this->fetchRows($cursor);
+ if (!empty($rows)) {
+ $models = $this->createModels($rows);
+ if (!empty($this->with)) {
+ $this->findWith($this->with, $models);
+ }
+ if (!$this->asArray) {
+ foreach ($models as $model) {
+ $model->afterFind();
+ }
+ }
- /**
- * Returns the Mongo collection for this query.
- * @param Connection $db Mongo connection.
- * @return Collection collection instance.
- */
- public function getCollection($db = null)
- {
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- if ($db === null) {
- $db = $modelClass::getDb();
- }
- if ($this->from === null) {
- $this->from = $modelClass::collectionName();
- }
- return $db->getCollection($this->from);
- }
+ return $models;
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * Executes query and returns a single row of result.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If null, the Mongo connection returned by [[modelClass]] will be used.
+ * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
+ * the query result may be either an array or an ActiveRecord object. Null will be returned
+ * if the query results in nothing.
+ */
+ public function one($db = null)
+ {
+ $row = parent::one($db);
+ if ($row !== false) {
+ if ($this->asArray) {
+ $model = $row;
+ } else {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $model = $class::instantiate($row);
+ $class::populateRecord($model, $row);
+ }
+ if (!empty($this->with)) {
+ $models = [$model];
+ $this->findWith($this->with, $models);
+ $model = $models[0];
+ }
+ if (!$this->asArray) {
+ $model->afterFind();
+ }
+
+ return $model;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the Mongo collection for this query.
+ * @param Connection $db Mongo connection.
+ * @return Collection collection instance.
+ */
+ public function getCollection($db = null)
+ {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+ if ($db === null) {
+ $db = $modelClass::getDb();
+ }
+ if ($this->from === null) {
+ $this->from = $modelClass::collectionName();
+ }
+
+ return $db->getCollection($this->from);
+ }
}
diff --git a/extensions/mongodb/ActiveRecord.php b/extensions/mongodb/ActiveRecord.php
index fdf73011e44..ebeda89f4cb 100644
--- a/extensions/mongodb/ActiveRecord.php
+++ b/extensions/mongodb/ActiveRecord.php
@@ -21,331 +21,339 @@
*/
abstract class ActiveRecord extends BaseActiveRecord
{
- /**
- * Returns the Mongo connection used by this AR class.
- * By default, the "mongodb" application component is used as the Mongo connection.
- * You may override this method if you want to use a different database connection.
- * @return Connection the database connection used by this AR class.
- */
- public static function getDb()
- {
- return \Yii::$app->getComponent('mongodb');
- }
+ /**
+ * Returns the Mongo connection used by this AR class.
+ * By default, the "mongodb" application component is used as the Mongo connection.
+ * You may override this method if you want to use a different database connection.
+ * @return Connection the database connection used by this AR class.
+ */
+ public static function getDb()
+ {
+ return \Yii::$app->getComponent('mongodb');
+ }
- /**
- * Updates all documents in the collection using the provided attribute values and conditions.
- * For example, to change the status to be 1 for all customers whose status is 2:
- *
- * ~~~
- * Customer::updateAll(['status' => 1], ['status' => 2]);
- * ~~~
- *
- * @param array $attributes attribute values (name-value pairs) to be saved into the collection
- * @param array $condition description of the objects to update.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $options list of options in format: optionName => optionValue.
- * @return integer the number of documents updated.
- */
- public static function updateAll($attributes, $condition = [], $options = [])
- {
- return static::getCollection()->update($condition, $attributes, $options);
- }
+ /**
+ * Updates all documents in the collection using the provided attribute values and conditions.
+ * For example, to change the status to be 1 for all customers whose status is 2:
+ *
+ * ~~~
+ * Customer::updateAll(['status' => 1], ['status' => 2]);
+ * ~~~
+ *
+ * @param array $attributes attribute values (name-value pairs) to be saved into the collection
+ * @param array $condition description of the objects to update.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return integer the number of documents updated.
+ */
+ public static function updateAll($attributes, $condition = [], $options = [])
+ {
+ return static::getCollection()->update($condition, $attributes, $options);
+ }
- /**
- * Updates all documents in the collection using the provided counter changes and conditions.
- * For example, to increment all customers' age by 1,
- *
- * ~~~
- * Customer::updateAllCounters(['age' => 1]);
- * ~~~
- *
- * @param array $counters the counters to be updated (attribute name => increment value).
- * Use negative values if you want to decrement the counters.
- * @param array $condition description of the objects to update.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $options list of options in format: optionName => optionValue.
- * @return integer the number of documents updated.
- */
- public static function updateAllCounters($counters, $condition = [], $options = [])
- {
- return static::getCollection()->update($condition, ['$inc' => $counters], $options);
- }
+ /**
+ * Updates all documents in the collection using the provided counter changes and conditions.
+ * For example, to increment all customers' age by 1,
+ *
+ * ~~~
+ * Customer::updateAllCounters(['age' => 1]);
+ * ~~~
+ *
+ * @param array $counters the counters to be updated (attribute name => increment value).
+ * Use negative values if you want to decrement the counters.
+ * @param array $condition description of the objects to update.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return integer the number of documents updated.
+ */
+ public static function updateAllCounters($counters, $condition = [], $options = [])
+ {
+ return static::getCollection()->update($condition, ['$inc' => $counters], $options);
+ }
- /**
- * Deletes documents in the collection using the provided conditions.
- * WARNING: If you do not specify any condition, this method will delete documents rows in the collection.
- *
- * For example, to delete all customers whose status is 3:
- *
- * ~~~
- * Customer::deleteAll(['status' => 3]);
- * ~~~
- *
- * @param array $condition description of the objects to delete.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $options list of options in format: optionName => optionValue.
- * @return integer the number of documents deleted.
- */
- public static function deleteAll($condition = [], $options = [])
- {
- return static::getCollection()->remove($condition, $options);
- }
+ /**
+ * Deletes documents in the collection using the provided conditions.
+ * WARNING: If you do not specify any condition, this method will delete documents rows in the collection.
+ *
+ * For example, to delete all customers whose status is 3:
+ *
+ * ~~~
+ * Customer::deleteAll(['status' => 3]);
+ * ~~~
+ *
+ * @param array $condition description of the objects to delete.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return integer the number of documents deleted.
+ */
+ public static function deleteAll($condition = [], $options = [])
+ {
+ return static::getCollection()->remove($condition, $options);
+ }
- /**
- * Creates an [[ActiveQuery]] instance.
- *
- * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
- * by [[hasOne()]] and [[hasMany()]] to create a relational query.
- * You may override this method to return a customized query (e.g. `CustomerQuery` specified
- * written for querying `Customer` purpose.)
- *
- * You may also define default conditions that should apply to all queries unless overridden:
- *
- * ```php
- * public static function createQuery($config = [])
- * {
- * return parent::createQuery($config)->where(['deleted' => false]);
- * }
- * ```
- *
- * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
- * default condition. Using [[Query::where()]] will override the default condition.
- *
- * @param array $config the configuration passed to the ActiveQuery class.
- * @return ActiveQuery the newly created [[ActiveQuery]] instance.
- */
- public static function createQuery($config = [])
- {
- $config['modelClass'] = get_called_class();
- return new ActiveQuery($config);
- }
+ /**
+ * Creates an [[ActiveQuery]] instance.
+ *
+ * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
+ * by [[hasOne()]] and [[hasMany()]] to create a relational query.
+ * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+ * written for querying `Customer` purpose.)
+ *
+ * You may also define default conditions that should apply to all queries unless overridden:
+ *
+ * ```php
+ * public static function createQuery($config = [])
+ * {
+ * return parent::createQuery($config)->where(['deleted' => false]);
+ * }
+ * ```
+ *
+ * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
+ * default condition. Using [[Query::where()]] will override the default condition.
+ *
+ * @param array $config the configuration passed to the ActiveQuery class.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance.
+ */
+ public static function createQuery($config = [])
+ {
+ $config['modelClass'] = get_called_class();
- /**
- * Declares the name of the Mongo collection associated with this AR class.
- * Collection name can be either a string or array:
- * - if string considered as the name of the collection inside the default database.
- * - if array - first element considered as the name of the database, second - as
- * name of collection inside that database
- * By default this method returns the class name as the collection name by calling [[Inflector::camel2id()]].
- * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
- * 'order_item'. You may override this method if the table is not named after this convention.
- * @return string|array the collection name
- */
- public static function collectionName()
- {
- return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
- }
+ return new ActiveQuery($config);
+ }
- /**
- * Return the Mongo collection instance for this AR class.
- * @return Collection collection instance.
- */
- public static function getCollection()
- {
- return static::getDb()->getCollection(static::collectionName());
- }
+ /**
+ * Declares the name of the Mongo collection associated with this AR class.
+ * Collection name can be either a string or array:
+ * - if string considered as the name of the collection inside the default database.
+ * - if array - first element considered as the name of the database, second - as
+ * name of collection inside that database
+ * By default this method returns the class name as the collection name by calling [[Inflector::camel2id()]].
+ * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
+ * 'order_item'. You may override this method if the table is not named after this convention.
+ * @return string|array the collection name
+ */
+ public static function collectionName()
+ {
+ return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
+ }
- /**
- * Returns the primary key name(s) for this AR class.
- * The default implementation will return ['_id'].
- *
- * Note that an array should be returned even for a collection with single primary key.
- *
- * @return string[] the primary keys of the associated Mongo collection.
- */
- public static function primaryKey()
- {
- return ['_id'];
- }
+ /**
+ * Return the Mongo collection instance for this AR class.
+ * @return Collection collection instance.
+ */
+ public static function getCollection()
+ {
+ return static::getDb()->getCollection(static::collectionName());
+ }
- /**
- * Returns the list of all attribute names of the model.
- * This method must be overridden by child classes to define available attributes.
- * Note: primary key attribute "_id" should be always present in returned array.
- * For example:
- * ~~~
- * public function attributes()
- * {
- * return ['_id', 'name', 'address', 'status'];
- * }
- * ~~~
- * @return array list of attribute names.
- */
- public function attributes()
- {
- throw new InvalidConfigException('The attributes() method of mongodb ActiveRecord has to be implemented by child classes.');
- }
+ /**
+ * Returns the primary key name(s) for this AR class.
+ * The default implementation will return ['_id'].
+ *
+ * Note that an array should be returned even for a collection with single primary key.
+ *
+ * @return string[] the primary keys of the associated Mongo collection.
+ */
+ public static function primaryKey()
+ {
+ return ['_id'];
+ }
- /**
- * Inserts a row into the associated Mongo collection using the attribute values of this record.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
- * fails, it will skip the rest of the steps;
- * 2. call [[afterValidate()]] when `$runValidation` is true.
- * 3. call [[beforeSave()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 4. insert the record into collection. If this fails, it will skip the rest of the steps;
- * 5. call [[afterSave()]];
- *
- * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
- * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
- * will be raised by the corresponding methods.
- *
- * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database.
- *
- * If the primary key is null during insertion, it will be populated with the actual
- * value after insertion.
- *
- * For example, to insert a customer record:
- *
- * ~~~
- * $customer = new Customer;
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->insert();
- * ~~~
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted into the collection.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded will be saved.
- * @return boolean whether the attributes are valid and the record is inserted successfully.
- * @throws \Exception in case insert failed.
- */
- public function insert($runValidation = true, $attributes = null)
- {
- if ($runValidation && !$this->validate($attributes)) {
- return false;
- }
- $result = $this->insertInternal($attributes);
- return $result;
- }
+ /**
+ * Returns the list of all attribute names of the model.
+ * This method must be overridden by child classes to define available attributes.
+ * Note: primary key attribute "_id" should be always present in returned array.
+ * For example:
+ * ~~~
+ * public function attributes()
+ * {
+ * return ['_id', 'name', 'address', 'status'];
+ * }
+ * ~~~
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ throw new InvalidConfigException('The attributes() method of mongodb ActiveRecord has to be implemented by child classes.');
+ }
- /**
- * @see ActiveRecord::insert()
- */
- protected function insertInternal($attributes = null)
- {
- if (!$this->beforeSave(true)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- $currentAttributes = $this->getAttributes();
- foreach ($this->primaryKey() as $key) {
- $values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null;
- }
- }
- $newId = static::getCollection()->insert($values);
- $this->setAttribute('_id', $newId);
- foreach ($values as $name => $value) {
- $this->setOldAttribute($name, $value);
- }
- $this->afterSave(true);
- return true;
- }
+ /**
+ * Inserts a row into the associated Mongo collection using the attribute values of this record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. insert the record into collection. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database.
+ *
+ * If the primary key is null during insertion, it will be populated with the actual
+ * value after insertion.
+ *
+ * For example, to insert a customer record:
+ *
+ * ~~~
+ * $customer = new Customer;
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->insert();
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the collection.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded will be saved.
+ * @return boolean whether the attributes are valid and the record is inserted successfully.
+ * @throws \Exception in case insert failed.
+ */
+ public function insert($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ $result = $this->insertInternal($attributes);
- /**
- * @see ActiveRecord::update()
- * @throws StaleObjectException
- */
- protected function updateInternal($attributes = null)
- {
- if (!$this->beforeSave(false)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- $this->afterSave(false);
- return 0;
- }
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- if (!isset($values[$lock])) {
- $values[$lock] = $this->$lock + 1;
- }
- $condition[$lock] = $this->$lock;
- }
- // We do not check the return value of update() because it's possible
- // that it doesn't change anything and thus returns 0.
- $rows = static::getCollection()->update($condition, $values);
+ return $result;
+ }
- if ($lock !== null && !$rows) {
- throw new StaleObjectException('The object being updated is outdated.');
- }
+ /**
+ * @see ActiveRecord::insert()
+ */
+ protected function insertInternal($attributes = null)
+ {
+ if (!$this->beforeSave(true)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ $currentAttributes = $this->getAttributes();
+ foreach ($this->primaryKey() as $key) {
+ $values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null;
+ }
+ }
+ $newId = static::getCollection()->insert($values);
+ $this->setAttribute('_id', $newId);
+ foreach ($values as $name => $value) {
+ $this->setOldAttribute($name, $value);
+ }
+ $this->afterSave(true);
- foreach ($values as $name => $value) {
- $this->setOldAttribute($name, $this->getAttribute($name));
- }
- $this->afterSave(false);
- return $rows;
- }
+ return true;
+ }
- /**
- * Deletes the document corresponding to this active record from the collection.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 2. delete the document from the collection;
- * 3. call [[afterDelete()]].
- *
- * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
- * will be raised by the corresponding methods.
- *
- * @return integer|boolean the number of documents deleted, or false if the deletion is unsuccessful for some reason.
- * Note that it is possible the number of documents deleted is 0, even though the deletion execution is successful.
- * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
- * being deleted is outdated.
- * @throws \Exception in case delete failed.
- */
- public function delete()
- {
- $result = false;
- if ($this->beforeDelete()) {
- $result = $this->deleteInternal();
- $this->afterDelete();
- }
- return $result;
- }
+ /**
+ * @see ActiveRecord::update()
+ * @throws StaleObjectException
+ */
+ protected function updateInternal($attributes = null)
+ {
+ if (!$this->beforeSave(false)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ $this->afterSave(false);
- /**
- * @see ActiveRecord::delete()
- * @throws StaleObjectException
- */
- protected function deleteInternal()
- {
- // we do not check the return value of deleteAll() because it's possible
- // the record is already deleted in the database and thus the method will return 0
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- $condition[$lock] = $this->$lock;
- }
- $result = static::getCollection()->remove($condition);
- if ($lock !== null && !$result) {
- throw new StaleObjectException('The object being deleted is outdated.');
- }
- $this->setOldAttributes(null);
- return $result;
- }
+ return 0;
+ }
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ if (!isset($values[$lock])) {
+ $values[$lock] = $this->$lock + 1;
+ }
+ $condition[$lock] = $this->$lock;
+ }
+ // We do not check the return value of update() because it's possible
+ // that it doesn't change anything and thus returns 0.
+ $rows = static::getCollection()->update($condition, $values);
- /**
- * Returns a value indicating whether the given active record is the same as the current one.
- * The comparison is made by comparing the table names and the primary key values of the two active records.
- * If one of the records [[isNewRecord|is new]] they are also considered not equal.
- * @param ActiveRecord $record record to compare to
- * @return boolean whether the two active records refer to the same row in the same Mongo collection.
- */
- public function equals($record)
- {
- if ($this->isNewRecord || $record->isNewRecord) {
- return false;
- }
- return $this->collectionName() === $record->collectionName() && (string)$this->getPrimaryKey() === (string)$record->getPrimaryKey();
- }
+ if ($lock !== null && !$rows) {
+ throw new StaleObjectException('The object being updated is outdated.');
+ }
+
+ foreach ($values as $name => $value) {
+ $this->setOldAttribute($name, $this->getAttribute($name));
+ }
+ $this->afterSave(false);
+
+ return $rows;
+ }
+
+ /**
+ * Deletes the document corresponding to this active record from the collection.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 2. delete the document from the collection;
+ * 3. call [[afterDelete()]].
+ *
+ * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
+ * will be raised by the corresponding methods.
+ *
+ * @return integer|boolean the number of documents deleted, or false if the deletion is unsuccessful for some reason.
+ * Note that it is possible the number of documents deleted is 0, even though the deletion execution is successful.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being deleted is outdated.
+ * @throws \Exception in case delete failed.
+ */
+ public function delete()
+ {
+ $result = false;
+ if ($this->beforeDelete()) {
+ $result = $this->deleteInternal();
+ $this->afterDelete();
+ }
+
+ return $result;
+ }
+
+ /**
+ * @see ActiveRecord::delete()
+ * @throws StaleObjectException
+ */
+ protected function deleteInternal()
+ {
+ // we do not check the return value of deleteAll() because it's possible
+ // the record is already deleted in the database and thus the method will return 0
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ $condition[$lock] = $this->$lock;
+ }
+ $result = static::getCollection()->remove($condition);
+ if ($lock !== null && !$result) {
+ throw new StaleObjectException('The object being deleted is outdated.');
+ }
+ $this->setOldAttributes(null);
+
+ return $result;
+ }
+
+ /**
+ * Returns a value indicating whether the given active record is the same as the current one.
+ * The comparison is made by comparing the table names and the primary key values of the two active records.
+ * If one of the records [[isNewRecord|is new]] they are also considered not equal.
+ * @param ActiveRecord $record record to compare to
+ * @return boolean whether the two active records refer to the same row in the same Mongo collection.
+ */
+ public function equals($record)
+ {
+ if ($this->isNewRecord || $record->isNewRecord) {
+ return false;
+ }
+
+ return $this->collectionName() === $record->collectionName() && (string) $this->getPrimaryKey() === (string) $record->getPrimaryKey();
+ }
}
diff --git a/extensions/mongodb/Cache.php b/extensions/mongodb/Cache.php
index af016a51c20..7f4edce6c86 100644
--- a/extensions/mongodb/Cache.php
+++ b/extensions/mongodb/Cache.php
@@ -34,169 +34,173 @@
*/
class Cache extends \yii\caching\Cache
{
- /**
- * @var Connection|string the MongoDB connection object or the application component ID of the MongoDB connection.
- * After the Cache object is created, if you want to change this property, you should only assign it
- * with a MongoDB connection object.
- */
- public $db = 'mongodb';
- /**
- * @var string|array the name of the MongoDB collection that stores the cache data.
- * Please refer to [[Connection::getCollection()]] on how to specify this parameter.
- * This collection is better to be pre-created with fields 'id' and 'expire' indexed.
- */
- public $cacheCollection = 'cache';
- /**
- * @var integer the probability (parts per million) that garbage collection (GC) should be performed
- * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
- * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
- */
- public $gcProbability = 100;
-
- /**
- * Initializes the Cache component.
- * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection.
- * @throws InvalidConfigException if [[db]] is invalid.
- */
- public function init()
- {
- parent::init();
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!$this->db instanceof Connection) {
- throw new InvalidConfigException($this->className() . "::db must be either a MongoDB connection instance or the application component ID of a MongoDB connection.");
- }
- }
-
- /**
- * Retrieves a value from cache with a specified key.
- * This method should be implemented by child classes to retrieve the data
- * from specific cache storage.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- $query = new Query;
- $row = $query->select(['data'])
- ->from($this->cacheCollection)
- ->where([
- 'id' => $key,
- '$or' => [
- [
- 'expire' => 0
- ],
- [
- 'expire' => ['$gt' => time()]
- ],
- ],
- ])
- ->one($this->db);
- if (empty($row)) {
- return false;
- } else {
- return $row['data'];
- }
- }
-
- /**
- * Stores a value identified by a key in cache.
- * This method should be implemented by child classes to store the data
- * in specific cache storage.
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- $result = $this->db->getCollection($this->cacheCollection)
- ->update(['id' => $key], [
- 'expire' => $expire > 0 ? $expire + time() : 0,
- 'data' => $value,
- ]);
-
- if ($result) {
- $this->gc();
- return true;
- } else {
- return $this->addValue($key, $value, $expire);
- }
- }
-
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This method should be implemented by child classes to store the data
- * in specific cache storage.
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- $this->gc();
-
- if ($expire > 0) {
- $expire += time();
- } else {
- $expire = 0;
- }
-
- try {
- $this->db->getCollection($this->cacheCollection)
- ->insert([
- 'id' => $key,
- 'expire' => $expire,
- 'data' => $value,
- ]);
- return true;
- } catch (\Exception $e) {
- return false;
- }
- }
-
- /**
- * Deletes a value with the specified key from cache
- * This method should be implemented by child classes to delete the data from actual cache storage.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- $this->db->getCollection($this->cacheCollection)
- ->remove(['id' => $key]);
- return true;
- }
-
- /**
- * Deletes all values from cache.
- * Child classes may implement this method to realize the flush operation.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- $this->db->getCollection($this->cacheCollection)
- ->remove();
- return true;
- }
-
- /**
- * Removes the expired data values.
- * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
- * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
- */
- public function gc($force = false)
- {
- if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
- $this->db->getCollection($this->cacheCollection)
- ->remove([
- 'expire' => [
- '$gt' => 0,
- '$lt' => time(),
- ]
- ]);
- }
- }
+ /**
+ * @var Connection|string the MongoDB connection object or the application component ID of the MongoDB connection.
+ * After the Cache object is created, if you want to change this property, you should only assign it
+ * with a MongoDB connection object.
+ */
+ public $db = 'mongodb';
+ /**
+ * @var string|array the name of the MongoDB collection that stores the cache data.
+ * Please refer to [[Connection::getCollection()]] on how to specify this parameter.
+ * This collection is better to be pre-created with fields 'id' and 'expire' indexed.
+ */
+ public $cacheCollection = 'cache';
+ /**
+ * @var integer the probability (parts per million) that garbage collection (GC) should be performed
+ * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
+ * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
+ */
+ public $gcProbability = 100;
+
+ /**
+ * Initializes the Cache component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException($this->className() . "::db must be either a MongoDB connection instance or the application component ID of a MongoDB connection.");
+ }
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This method should be implemented by child classes to retrieve the data
+ * from specific cache storage.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ $query = new Query;
+ $row = $query->select(['data'])
+ ->from($this->cacheCollection)
+ ->where([
+ 'id' => $key,
+ '$or' => [
+ [
+ 'expire' => 0
+ ],
+ [
+ 'expire' => ['$gt' => time()]
+ ],
+ ],
+ ])
+ ->one($this->db);
+ if (empty($row)) {
+ return false;
+ } else {
+ return $row['data'];
+ }
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ $result = $this->db->getCollection($this->cacheCollection)
+ ->update(['id' => $key], [
+ 'expire' => $expire > 0 ? $expire + time() : 0,
+ 'data' => $value,
+ ]);
+
+ if ($result) {
+ $this->gc();
+
+ return true;
+ } else {
+ return $this->addValue($key, $value, $expire);
+ }
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ $this->gc();
+
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
+
+ try {
+ $this->db->getCollection($this->cacheCollection)
+ ->insert([
+ 'id' => $key,
+ 'expire' => $expire,
+ 'data' => $value,
+ ]);
+
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This method should be implemented by child classes to delete the data from actual cache storage.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ $this->db->getCollection($this->cacheCollection)
+ ->remove(['id' => $key]);
+
+ return true;
+ }
+
+ /**
+ * Deletes all values from cache.
+ * Child classes may implement this method to realize the flush operation.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ $this->db->getCollection($this->cacheCollection)
+ ->remove();
+
+ return true;
+ }
+
+ /**
+ * Removes the expired data values.
+ * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
+ * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
+ */
+ public function gc($force = false)
+ {
+ if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+ $this->db->getCollection($this->cacheCollection)
+ ->remove([
+ 'expire' => [
+ '$gt' => 0,
+ '$lt' => time(),
+ ]
+ ]);
+ }
+ }
}
diff --git a/extensions/mongodb/Collection.php b/extensions/mongodb/Collection.php
index 3a5a9108490..237fb086959 100644
--- a/extensions/mongodb/Collection.php
+++ b/extensions/mongodb/Collection.php
@@ -68,932 +68,954 @@
*/
class Collection extends Object
{
- /**
- * @var \MongoCollection Mongo collection instance.
- */
- public $mongoCollection;
-
- /**
- * @return string name of this collection.
- */
- public function getName()
- {
- return $this->mongoCollection->getName();
- }
-
- /**
- * @return string full name of this collection, including database name.
- */
- public function getFullName()
- {
- return $this->mongoCollection->__toString();
- }
-
- /**
- * @return array last error information.
- */
- public function getLastError()
- {
- return $this->mongoCollection->db->lastError();
- }
-
- /**
- * Composes log/profile token.
- * @param string $command command name
- * @param array $arguments command arguments.
- * @return string token.
- */
- protected function composeLogToken($command, $arguments = [])
- {
- $parts = [];
- foreach ($arguments as $argument) {
- $parts[] = is_scalar($argument) ? $argument : $this->encodeLogData($argument);
- }
- return $this->getFullName() . '.' . $command . '(' . implode(', ', $parts) . ')';
- }
-
- /**
- * Encodes complex log data into JSON format string.
- * @param mixed $data raw data.
- * @return string encoded data string.
- */
- protected function encodeLogData($data)
- {
- return json_encode($this->processLogData($data));
- }
-
- /**
- * Pre-processes the log data before sending it to `json_encode()`.
- * @param mixed $data raw data.
- * @return mixed the processed data.
- */
- protected function processLogData($data)
- {
- if (is_object($data)) {
- if ($data instanceof \MongoId ||
- $data instanceof \MongoRegex ||
- $data instanceof \MongoDate ||
- $data instanceof \MongoInt32 ||
- $data instanceof \MongoInt64 ||
- $data instanceof \MongoTimestamp
- ) {
- $data = get_class($data) . '(' . $data->__toString() . ')';
- } elseif ($data instanceof \MongoCode) {
- $data = 'MongoCode( ' . $data->__toString() . ' )';
- } elseif ($data instanceof \MongoBinData) {
- $data = 'MongoBinData(...)';
- } elseif ($data instanceof \MongoDBRef) {
- $data = 'MongoDBRef(...)';
- } elseif ($data instanceof \MongoMinKey || $data instanceof \MongoMaxKey) {
- $data = get_class($data);
- } else {
- $result = [];
- foreach ($data as $name => $value) {
- $result[$name] = $value;
- }
- $data = $result;
- }
-
- if ($data === []) {
- return new \stdClass();
- }
- }
-
- if (is_array($data)) {
- foreach ($data as $key => $value) {
- if (is_array($value) || is_object($value)) {
- $data[$key] = $this->processLogData($value);
- }
- }
- }
-
- return $data;
- }
-
- /**
- * Drops this collection.
- * @throws Exception on failure.
- * @return boolean whether the operation successful.
- */
- public function drop()
- {
- $token = $this->composeLogToken('drop');
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->drop();
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- return true;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Creates an index on the collection and the specified fields.
- * @param array|string $columns column name or list of column names.
- * If array is given, each element in the array has as key the field name, and as
- * value either 1 for ascending sort, or -1 for descending sort.
- * You can specify field using native numeric key with the field name as a value,
- * in this case ascending sort will be used.
- * For example:
- * ~~~
- * [
- * 'name',
- * 'status' => -1,
- * ]
- * ~~~
- * @param array $options list of options in format: optionName => optionValue.
- * @throws Exception on failure.
- * @return boolean whether the operation successful.
- */
- public function createIndex($columns, $options = [])
- {
- if (!is_array($columns)) {
- $columns = [$columns];
- }
- $keys = $this->normalizeIndexKeys($columns);
- $token = $this->composeLogToken('createIndex', [$keys, $options]);
- $options = array_merge(['w' => 1], $options);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->ensureIndex($keys, $options);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- return true;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Drop indexes for specified column(s).
- * @param string|array $columns column name or list of column names.
- * If array is given, each element in the array has as key the field name, and as
- * value either 1 for ascending sort, or -1 for descending sort.
- * Use value 'text' to specify text index.
- * You can specify field using native numeric key with the field name as a value,
- * in this case ascending sort will be used.
- * For example:
- * ~~~
- * [
- * 'name',
- * 'status' => -1,
- * 'description' => 'text',
- * ]
- * ~~~
- * @throws Exception on failure.
- * @return boolean whether the operation successful.
- */
- public function dropIndex($columns)
- {
- if (!is_array($columns)) {
- $columns = [$columns];
- }
- $keys = $this->normalizeIndexKeys($columns);
- $token = $this->composeLogToken('dropIndex', [$keys]);
- Yii::info($token, __METHOD__);
- try {
- $result = $this->mongoCollection->deleteIndex($keys);
- $this->tryResultError($result);
- return true;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Compose index keys from given columns/keys list.
- * @param array $columns raw columns/keys list.
- * @return array normalizes index keys array.
- */
- protected function normalizeIndexKeys($columns)
- {
- $keys = [];
- foreach ($columns as $key => $value) {
- if (is_numeric($key)) {
- $keys[$value] = \MongoCollection::ASCENDING;
- } else {
- $keys[$key] = $value;
- }
- }
- return $keys;
- }
-
- /**
- * Drops all indexes for this collection.
- * @throws Exception on failure.
- * @return integer count of dropped indexes.
- */
- public function dropAllIndexes()
- {
- $token = $this->composeLogToken('dropIndexes');
- Yii::info($token, __METHOD__);
- try {
- $result = $this->mongoCollection->deleteIndexes();
- $this->tryResultError($result);
- return $result['nIndexesWas'];
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Returns a cursor for the search results.
- * In order to perform "find" queries use [[Query]] class.
- * @param array $condition query condition
- * @param array $fields fields to be selected
- * @return \MongoCursor cursor for the search results
- * @see Query
- */
- public function find($condition = [], $fields = [])
- {
- return $this->mongoCollection->find($this->buildCondition($condition), $fields);
- }
-
- /**
- * Returns a single document.
- * @param array $condition query condition
- * @param array $fields fields to be selected
- * @return array|null the single document. Null is returned if the query results in nothing.
- * @see http://www.php.net/manual/en/mongocollection.findone.php
- */
- public function findOne($condition = [], $fields = [])
- {
- return $this->mongoCollection->findOne($this->buildCondition($condition), $fields);
- }
-
- /**
- * Updates a document and returns it.
- * @param array $condition query condition
- * @param array $update update criteria
- * @param array $fields fields to be returned
- * @param array $options list of options in format: optionName => optionValue.
- * @return array|null the original document, or the modified document when $options['new'] is set.
- * @throws Exception on failure.
- * @see http://www.php.net/manual/en/mongocollection.findandmodify.php
- */
- public function findAndModify($condition, $update, $fields = [], $options = [])
- {
- $condition = $this->buildCondition($condition);
- $token = $this->composeLogToken('findAndModify', [$condition, $update, $fields, $options]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->findAndModify($condition, $update, $fields, $options);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Inserts new data into collection.
- * @param array|object $data data to be inserted.
- * @param array $options list of options in format: optionName => optionValue.
- * @return \MongoId new record id instance.
- * @throws Exception on failure.
- */
- public function insert($data, $options = [])
- {
- $token = $this->composeLogToken('insert', [$data]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $options = array_merge(['w' => 1], $options);
- $this->tryResultError($this->mongoCollection->insert($data, $options));
- Yii::endProfile($token, __METHOD__);
- return is_array($data) ? $data['_id'] : $data->_id;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Inserts several new rows into collection.
- * @param array $rows array of arrays or objects to be inserted.
- * @param array $options list of options in format: optionName => optionValue.
- * @return array inserted data, each row will have "_id" key assigned to it.
- * @throws Exception on failure.
- */
- public function batchInsert($rows, $options = [])
- {
- $token = $this->composeLogToken('batchInsert', [$rows]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $options = array_merge(['w' => 1], $options);
- $this->tryResultError($this->mongoCollection->batchInsert($rows, $options));
- Yii::endProfile($token, __METHOD__);
- return $rows;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Updates the rows, which matches given criteria by given data.
- * Note: for "multiple" mode Mongo requires explicit strategy "$set" or "$inc"
- * to be specified for the "newData". If no strategy is passed "$set" will be used.
- * @param array $condition description of the objects to update.
- * @param array $newData the object with which to update the matching records.
- * @param array $options list of options in format: optionName => optionValue.
- * @return integer|boolean number of updated documents or whether operation was successful.
- * @throws Exception on failure.
- */
- public function update($condition, $newData, $options = [])
- {
- $condition = $this->buildCondition($condition);
- $options = array_merge(['w' => 1, 'multiple' => true], $options);
- if ($options['multiple']) {
- $keys = array_keys($newData);
- if (!empty($keys) && strncmp('$', $keys[0], 1) !== 0) {
- $newData = ['$set' => $newData];
- }
- }
- $token = $this->composeLogToken('update', [$condition, $newData, $options]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->update($condition, $newData, $options);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- if (is_array($result) && array_key_exists('n', $result)) {
- return $result['n'];
- } else {
- return true;
- }
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Update the existing database data, otherwise insert this data
- * @param array|object $data data to be updated/inserted.
- * @param array $options list of options in format: optionName => optionValue.
- * @return \MongoId updated/new record id instance.
- * @throws Exception on failure.
- */
- public function save($data, $options = [])
- {
- $token = $this->composeLogToken('save', [$data]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $options = array_merge(['w' => 1], $options);
- $this->tryResultError($this->mongoCollection->save($data, $options));
- Yii::endProfile($token, __METHOD__);
- return is_array($data) ? $data['_id'] : $data->_id;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Removes data from the collection.
- * @param array $condition description of records to remove.
- * @param array $options list of options in format: optionName => optionValue.
- * @return integer|boolean number of updated documents or whether operation was successful.
- * @throws Exception on failure.
- * @see http://www.php.net/manual/en/mongocollection.remove.php
- */
- public function remove($condition = [], $options = [])
- {
- $condition = $this->buildCondition($condition);
- $options = array_merge(['w' => 1, 'justOne' => false], $options);
- $token = $this->composeLogToken('remove', [$condition, $options]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->remove($condition, $options);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- if (is_array($result) && array_key_exists('n', $result)) {
- return $result['n'];
- } else {
- return true;
- }
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Returns a list of distinct values for the given column across a collection.
- * @param string $column column to use.
- * @param array $condition query parameters.
- * @return array|boolean array of distinct values, or "false" on failure.
- * @throws Exception on failure.
- */
- public function distinct($column, $condition = [])
- {
- $condition = $this->buildCondition($condition);
- $token = $this->composeLogToken('distinct', [$column, $condition]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->distinct($column, $condition);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Performs aggregation using Mongo Aggregation Framework.
- * @param array $pipeline list of pipeline operators, or just the first operator
- * @param array $pipelineOperator additional pipeline operator. You can specify additional
- * pipelines via third argument, fourth argument etc.
- * @return array the result of the aggregation.
- * @throws Exception on failure.
- * @see http://docs.mongodb.org/manual/applications/aggregation/
- */
- public function aggregate($pipeline, $pipelineOperator = [])
- {
- $args = func_get_args();
- $token = $this->composeLogToken('aggregate', $args);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = call_user_func_array([$this->mongoCollection, 'aggregate'], $args);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- return $result['result'];
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Performs aggregation using Mongo "group" command.
- * @param mixed $keys fields to group by. If an array or non-code object is passed,
- * it will be the key used to group results. If instance of [[\MongoCode]] passed,
- * it will be treated as a function that returns the key to group by.
- * @param array $initial Initial value of the aggregation counter object.
- * @param \MongoCode|string $reduce function that takes two arguments (the current
- * document and the aggregation to this point) and does the aggregation.
- * Argument will be automatically cast to [[\MongoCode]].
- * @param array $options optional parameters to the group command. Valid options include:
- * - condition - criteria for including a document in the aggregation.
- * - finalize - function called once per unique key that takes the final output of the reduce function.
- * @return array the result of the aggregation.
- * @throws Exception on failure.
- * @see http://docs.mongodb.org/manual/reference/command/group/
- */
- public function group($keys, $initial, $reduce, $options = [])
- {
- if (!($reduce instanceof \MongoCode)) {
- $reduce = new \MongoCode((string)$reduce);
- }
- if (array_key_exists('condition', $options)) {
- $options['condition'] = $this->buildCondition($options['condition']);
- }
- if (array_key_exists('finalize', $options)) {
- if (!($options['finalize'] instanceof \MongoCode)) {
- $options['finalize'] = new \MongoCode((string)$options['finalize']);
- }
- }
- $token = $this->composeLogToken('group', [$keys, $initial, $reduce, $options]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- // Avoid possible E_DEPRECATED for $options:
- if (empty($options)) {
- $result = $this->mongoCollection->group($keys, $initial, $reduce);
- } else {
- $result = $this->mongoCollection->group($keys, $initial, $reduce, $options);
- }
- $this->tryResultError($result);
-
- Yii::endProfile($token, __METHOD__);
- if (array_key_exists('retval', $result)) {
- return $result['retval'];
- } else {
- return [];
- }
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Performs aggregation using Mongo "map reduce" mechanism.
- * Note: this function will not return the aggregation result, instead it will
- * write it inside the another Mongo collection specified by "out" parameter.
- * For example:
- *
- * ~~~
- * $customerCollection = Yii::$app->mongo->getCollection('customer');
- * $resultCollectionName = $customerCollection->mapReduce(
- * 'function () {emit(this.status, this.amount)}',
- * 'function (key, values) {return Array.sum(values)}',
- * 'mapReduceOut',
- * ['status' => 3]
- * );
- * $query = new Query();
- * $results = $query->from($resultCollectionName)->all();
- * ~~~
- *
- * @param \MongoCode|string $map function, which emits map data from collection.
- * Argument will be automatically cast to [[\MongoCode]].
- * @param \MongoCode|string $reduce function that takes two arguments (the map key
- * and the map values) and does the aggregation.
- * Argument will be automatically cast to [[\MongoCode]].
- * @param string|array $out output collection name. It could be a string for simple output
- * ('outputCollection'), or an array for parametrized output (['merge' => 'outputCollection']).
- * You can pass ['inline' => true] to fetch the result at once without temporary collection usage.
- * @param array $condition criteria for including a document in the aggregation.
- * @param array $options additional optional parameters to the mapReduce command. Valid options include:
- * - sort - array - key to sort the input documents. The sort key must be in an existing index for this collection.
- * - limit - the maximum number of documents to return in the collection.
- * - finalize - function, which follows the reduce method and modifies the output.
- * - scope - array - specifies global variables that are accessible in the map, reduce and finalize functions.
- * - jsMode - boolean -Specifies whether to convert intermediate data into BSON format between the execution of the map and reduce functions.
- * - verbose - boolean - specifies whether to include the timing information in the result information.
- * @return string|array the map reduce output collection name or output results.
- * @throws Exception on failure.
- */
- public function mapReduce($map, $reduce, $out, $condition = [], $options = [])
- {
- if (!($map instanceof \MongoCode)) {
- $map = new \MongoCode((string)$map);
- }
- if (!($reduce instanceof \MongoCode)) {
- $reduce = new \MongoCode((string)$reduce);
- }
- $command = [
- 'mapReduce' => $this->getName(),
- 'map' => $map,
- 'reduce' => $reduce,
- 'out' => $out
- ];
- if (!empty($condition)) {
- $command['query'] = $this->buildCondition($condition);
- }
- if (array_key_exists('finalize', $options)) {
- if (!($options['finalize'] instanceof \MongoCode)) {
- $options['finalize'] = new \MongoCode((string)$options['finalize']);
- }
- }
- if (!empty($options)) {
- $command = array_merge($command, $options);
- }
- $token = $this->composeLogToken('mapReduce', [$map, $reduce, $out]);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $command = array_merge(['mapReduce' => $this->getName()], $command);
- $result = $this->mongoCollection->db->command($command);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- return array_key_exists('results', $result) ? $result['results'] : $result['result'];
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Performs full text search.
- * @param string $search string of terms that MongoDB parses and uses to query the text index.
- * @param array $condition criteria for filtering a results list.
- * @param array $fields list of fields to be returned in result.
- * @param array $options additional optional parameters to the mapReduce command. Valid options include:
- * - limit - the maximum number of documents to include in the response (by default 100).
- * - language - the language that determines the list of stop words for the search
- * and the rules for the stemmer and tokenizer. If not specified, the search uses the default
- * language of the index.
- * @return array the highest scoring documents, in descending order by score.
- * @throws Exception on failure.
- */
- public function fullTextSearch($search, $condition = [], $fields = [], $options = [])
- {
- $command = [
- 'search' => $search
- ];
- if (!empty($condition)) {
- $command['filter'] = $this->buildCondition($condition);
- }
- if (!empty($fields)) {
- $command['project'] = $fields;
- }
- if (!empty($options)) {
- $command = array_merge($command, $options);
- }
- $token = $this->composeLogToken('text', $command);
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $command = array_merge(['text' => $this->getName()], $command);
- $result = $this->mongoCollection->db->command($command);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- return $result['results'];
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Checks if command execution result ended with an error.
- * @param mixed $result raw command execution result.
- * @throws Exception if an error occurred.
- */
- protected function tryResultError($result)
- {
- if (is_array($result)) {
- if (!empty($result['errmsg'])) {
- $errorMessage = $result['errmsg'];
- } elseif (!empty($result['err'])) {
- $errorMessage = $result['err'];
- }
- if (isset($errorMessage)) {
- if (array_key_exists('code', $result)) {
- $errorCode = (int)$result['code'];
- } elseif (array_key_exists('ok', $result)) {
- $errorCode = (int)$result['ok'];
- } else {
- $errorCode = 0;
- }
- throw new Exception($errorMessage, $errorCode);
- }
- } elseif (!$result) {
- throw new Exception('Unknown error, use "w=1" option to enable error tracking');
- }
- }
-
- /**
- * Throws an exception if there was an error on the last operation.
- * @throws Exception if an error occurred.
- */
- protected function tryLastError()
- {
- $this->tryResultError($this->getLastError());
- }
-
- /**
- * Converts "\yii\db\*" quick condition keyword into actual Mongo condition keyword.
- * @param string $key raw condition key.
- * @return string actual key.
- */
- protected function normalizeConditionKeyword($key)
- {
- static $map = [
- 'OR' => '$or',
- 'IN' => '$in',
- 'NOT IN' => '$nin',
- ];
- $matchKey = strtoupper($key);
- if (array_key_exists($matchKey, $map)) {
- return $map[$matchKey];
- } else {
- return $key;
- }
- }
-
- /**
- * Converts given value into [[MongoId]] instance.
- * If array given, each element of it will be processed.
- * @param mixed $rawId raw id(s).
- * @return array|\MongoId normalized id(s).
- */
- protected function ensureMongoId($rawId)
- {
- if (is_array($rawId)) {
- $result = [];
- foreach ($rawId as $key => $value) {
- $result[$key] = $this->ensureMongoId($value);
- }
- return $result;
- } elseif (is_object($rawId)) {
- if ($rawId instanceof \MongoId) {
- return $rawId;
- } else {
- $rawId = (string)$rawId;
- }
- }
- try {
- $mongoId = new \MongoId($rawId);
- } catch (\MongoException $e) {
- // invalid id format
- $mongoId = $rawId;
- }
- return $mongoId;
- }
-
- /**
- * Parses the condition specification and generates the corresponding Mongo condition.
- * @param array $condition the condition specification. Please refer to [[Query::where()]]
- * on how to specify a condition.
- * @return array the generated Mongo condition
- * @throws InvalidParamException if the condition is in bad format
- */
- public function buildCondition($condition)
- {
- static $builders = [
- 'AND' => 'buildAndCondition',
- 'OR' => 'buildOrCondition',
- 'BETWEEN' => 'buildBetweenCondition',
- 'NOT BETWEEN' => 'buildBetweenCondition',
- 'IN' => 'buildInCondition',
- 'NOT IN' => 'buildInCondition',
- 'LIKE' => 'buildLikeCondition',
- ];
-
- if (!is_array($condition)) {
- throw new InvalidParamException('Condition should be an array.');
- } elseif (empty($condition)) {
- return [];
- }
- if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
- $operator = strtoupper($condition[0]);
- if (isset($builders[$operator])) {
- $method = $builders[$operator];
- array_shift($condition);
- return $this->$method($operator, $condition);
- } else {
- throw new InvalidParamException('Found unknown operator in query: ' . $operator);
- }
- } else {
- // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
- return $this->buildHashCondition($condition);
- }
- }
-
- /**
- * Creates a condition based on column-value pairs.
- * @param array $condition the condition specification.
- * @return array the generated Mongo condition.
- */
- public function buildHashCondition($condition)
- {
- $result = [];
- foreach ($condition as $name => $value) {
- if (strncmp('$', $name, 1) === 0) {
- // Native Mongo condition:
- $result[$name] = $value;
- } else {
- if (is_array($value)) {
- if (array_key_exists(0, $value)) {
- // Quick IN condition:
- $result = array_merge($result, $this->buildInCondition('IN', [$name, $value]));
- } else {
- // Mongo complex condition:
- $result[$name] = $value;
- }
- } else {
- // Direct match:
- if ($name == '_id') {
- $value = $this->ensureMongoId($value);
- }
- $result[$name] = $value;
- }
- }
- }
- return $result;
- }
-
- /**
- * Connects two or more conditions with the `AND` operator.
- * @param string $operator the operator to use for connecting the given operands
- * @param array $operands the Mongo conditions to connect.
- * @return array the generated Mongo condition.
- */
- public function buildAndCondition($operator, $operands)
- {
- $result = [];
- foreach ($operands as $operand) {
- $condition = $this->buildCondition($operand);
- $result = array_merge_recursive($result, $condition);
- }
- return $result;
- }
-
- /**
- * Connects two or more conditions with the `OR` operator.
- * @param string $operator the operator to use for connecting the given operands
- * @param array $operands the Mongo conditions to connect.
- * @return array the generated Mongo condition.
- */
- public function buildOrCondition($operator, $operands)
- {
- $operator = $this->normalizeConditionKeyword($operator);
- $parts = [];
- foreach ($operands as $operand) {
- $parts[] = $this->buildCondition($operand);
- }
- return [$operator => $parts];
- }
-
- /**
- * Creates an Mongo condition, which emulates the `BETWEEN` operator.
- * @param string $operator the operator to use
- * @param array $operands the first operand is the column name. The second and third operands
- * describe the interval that column value should be in.
- * @return array the generated Mongo condition.
- * @throws InvalidParamException if wrong number of operands have been given.
- */
- public function buildBetweenCondition($operator, $operands)
- {
- if (!isset($operands[0], $operands[1], $operands[2])) {
- throw new InvalidParamException("Operator '$operator' requires three operands.");
- }
- list($column, $value1, $value2) = $operands;
- if (strncmp('NOT', $operator, 3) === 0) {
- return [
- $column => [
- '$lt' => $value1,
- '$gt' => $value2,
- ]
- ];
- } else {
- return [
- $column => [
- '$gte' => $value1,
- '$lte' => $value2,
- ]
- ];
- }
- }
-
- /**
- * Creates an Mongo condition with the `IN` operator.
- * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
- * @param array $operands the first operand is the column name. If it is an array
- * a composite IN condition will be generated.
- * The second operand is an array of values that column value should be among.
- * @return array the generated Mongo condition.
- * @throws InvalidParamException if wrong number of operands have been given.
- */
- public function buildInCondition($operator, $operands)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new InvalidParamException("Operator '$operator' requires two operands.");
- }
-
- list($column, $values) = $operands;
-
- $values = (array)$values;
-
- if (!is_array($column)) {
- $columns = [$column];
- $values = [$column => $values];
- } elseif (count($column) < 2) {
- $columns = $column;
- $values = [$column[0] => $values];
- } else {
- $columns = $column;
- }
-
- $operator = $this->normalizeConditionKeyword($operator);
- $result = [];
- foreach ($columns as $column) {
- if ($column == '_id') {
- $inValues = $this->ensureMongoId($values[$column]);
- } else {
- $inValues = $values[$column];
- }
- $result[$column][$operator] = $inValues;
- }
- return $result;
- }
-
- /**
- * Creates a Mongo condition, which emulates the `LIKE` operator.
- * @param string $operator the operator to use
- * @param array $operands the first operand is the column name.
- * The second operand is a single value that column value should be compared with.
- * @return array the generated Mongo condition.
- * @throws InvalidParamException if wrong number of operands have been given.
- */
- public function buildLikeCondition($operator, $operands)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new InvalidParamException("Operator '$operator' requires two operands.");
- }
- list($column, $value) = $operands;
- if (!($value instanceof \MongoRegex)) {
- $value = new \MongoRegex($value);
- }
- return [$column => $value];
- }
+ /**
+ * @var \MongoCollection Mongo collection instance.
+ */
+ public $mongoCollection;
+
+ /**
+ * @return string name of this collection.
+ */
+ public function getName()
+ {
+ return $this->mongoCollection->getName();
+ }
+
+ /**
+ * @return string full name of this collection, including database name.
+ */
+ public function getFullName()
+ {
+ return $this->mongoCollection->__toString();
+ }
+
+ /**
+ * @return array last error information.
+ */
+ public function getLastError()
+ {
+ return $this->mongoCollection->db->lastError();
+ }
+
+ /**
+ * Composes log/profile token.
+ * @param string $command command name
+ * @param array $arguments command arguments.
+ * @return string token.
+ */
+ protected function composeLogToken($command, $arguments = [])
+ {
+ $parts = [];
+ foreach ($arguments as $argument) {
+ $parts[] = is_scalar($argument) ? $argument : $this->encodeLogData($argument);
+ }
+
+ return $this->getFullName() . '.' . $command . '(' . implode(', ', $parts) . ')';
+ }
+
+ /**
+ * Encodes complex log data into JSON format string.
+ * @param mixed $data raw data.
+ * @return string encoded data string.
+ */
+ protected function encodeLogData($data)
+ {
+ return json_encode($this->processLogData($data));
+ }
+
+ /**
+ * Pre-processes the log data before sending it to `json_encode()`.
+ * @param mixed $data raw data.
+ * @return mixed the processed data.
+ */
+ protected function processLogData($data)
+ {
+ if (is_object($data)) {
+ if ($data instanceof \MongoId ||
+ $data instanceof \MongoRegex ||
+ $data instanceof \MongoDate ||
+ $data instanceof \MongoInt32 ||
+ $data instanceof \MongoInt64 ||
+ $data instanceof \MongoTimestamp
+ ) {
+ $data = get_class($data) . '(' . $data->__toString() . ')';
+ } elseif ($data instanceof \MongoCode) {
+ $data = 'MongoCode( ' . $data->__toString() . ' )';
+ } elseif ($data instanceof \MongoBinData) {
+ $data = 'MongoBinData(...)';
+ } elseif ($data instanceof \MongoDBRef) {
+ $data = 'MongoDBRef(...)';
+ } elseif ($data instanceof \MongoMinKey || $data instanceof \MongoMaxKey) {
+ $data = get_class($data);
+ } else {
+ $result = [];
+ foreach ($data as $name => $value) {
+ $result[$name] = $value;
+ }
+ $data = $result;
+ }
+
+ if ($data === []) {
+ return new \stdClass();
+ }
+ }
+
+ if (is_array($data)) {
+ foreach ($data as $key => $value) {
+ if (is_array($value) || is_object($value)) {
+ $data[$key] = $this->processLogData($value);
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Drops this collection.
+ * @throws Exception on failure.
+ * @return boolean whether the operation successful.
+ */
+ public function drop()
+ {
+ $token = $this->composeLogToken('drop');
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->drop();
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+
+ return true;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Creates an index on the collection and the specified fields.
+ * @param array|string $columns column name or list of column names.
+ * If array is given, each element in the array has as key the field name, and as
+ * value either 1 for ascending sort, or -1 for descending sort.
+ * You can specify field using native numeric key with the field name as a value,
+ * in this case ascending sort will be used.
+ * For example:
+ * ~~~
+ * [
+ * 'name',
+ * 'status' => -1,
+ * ]
+ * ~~~
+ * @param array $options list of options in format: optionName => optionValue.
+ * @throws Exception on failure.
+ * @return boolean whether the operation successful.
+ */
+ public function createIndex($columns, $options = [])
+ {
+ if (!is_array($columns)) {
+ $columns = [$columns];
+ }
+ $keys = $this->normalizeIndexKeys($columns);
+ $token = $this->composeLogToken('createIndex', [$keys, $options]);
+ $options = array_merge(['w' => 1], $options);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->ensureIndex($keys, $options);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+
+ return true;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Drop indexes for specified column(s).
+ * @param string|array $columns column name or list of column names.
+ * If array is given, each element in the array has as key the field name, and as
+ * value either 1 for ascending sort, or -1 for descending sort.
+ * Use value 'text' to specify text index.
+ * You can specify field using native numeric key with the field name as a value,
+ * in this case ascending sort will be used.
+ * For example:
+ * ~~~
+ * [
+ * 'name',
+ * 'status' => -1,
+ * 'description' => 'text',
+ * ]
+ * ~~~
+ * @throws Exception on failure.
+ * @return boolean whether the operation successful.
+ */
+ public function dropIndex($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = [$columns];
+ }
+ $keys = $this->normalizeIndexKeys($columns);
+ $token = $this->composeLogToken('dropIndex', [$keys]);
+ Yii::info($token, __METHOD__);
+ try {
+ $result = $this->mongoCollection->deleteIndex($keys);
+ $this->tryResultError($result);
+
+ return true;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Compose index keys from given columns/keys list.
+ * @param array $columns raw columns/keys list.
+ * @return array normalizes index keys array.
+ */
+ protected function normalizeIndexKeys($columns)
+ {
+ $keys = [];
+ foreach ($columns as $key => $value) {
+ if (is_numeric($key)) {
+ $keys[$value] = \MongoCollection::ASCENDING;
+ } else {
+ $keys[$key] = $value;
+ }
+ }
+
+ return $keys;
+ }
+
+ /**
+ * Drops all indexes for this collection.
+ * @throws Exception on failure.
+ * @return integer count of dropped indexes.
+ */
+ public function dropAllIndexes()
+ {
+ $token = $this->composeLogToken('dropIndexes');
+ Yii::info($token, __METHOD__);
+ try {
+ $result = $this->mongoCollection->deleteIndexes();
+ $this->tryResultError($result);
+
+ return $result['nIndexesWas'];
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns a cursor for the search results.
+ * In order to perform "find" queries use [[Query]] class.
+ * @param array $condition query condition
+ * @param array $fields fields to be selected
+ * @return \MongoCursor cursor for the search results
+ * @see Query
+ */
+ public function find($condition = [], $fields = [])
+ {
+ return $this->mongoCollection->find($this->buildCondition($condition), $fields);
+ }
+
+ /**
+ * Returns a single document.
+ * @param array $condition query condition
+ * @param array $fields fields to be selected
+ * @return array|null the single document. Null is returned if the query results in nothing.
+ * @see http://www.php.net/manual/en/mongocollection.findone.php
+ */
+ public function findOne($condition = [], $fields = [])
+ {
+ return $this->mongoCollection->findOne($this->buildCondition($condition), $fields);
+ }
+
+ /**
+ * Updates a document and returns it.
+ * @param array $condition query condition
+ * @param array $update update criteria
+ * @param array $fields fields to be returned
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return array|null the original document, or the modified document when $options['new'] is set.
+ * @throws Exception on failure.
+ * @see http://www.php.net/manual/en/mongocollection.findandmodify.php
+ */
+ public function findAndModify($condition, $update, $fields = [], $options = [])
+ {
+ $condition = $this->buildCondition($condition);
+ $token = $this->composeLogToken('findAndModify', [$condition, $update, $fields, $options]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->findAndModify($condition, $update, $fields, $options);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Inserts new data into collection.
+ * @param array|object $data data to be inserted.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return \MongoId new record id instance.
+ * @throws Exception on failure.
+ */
+ public function insert($data, $options = [])
+ {
+ $token = $this->composeLogToken('insert', [$data]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $options = array_merge(['w' => 1], $options);
+ $this->tryResultError($this->mongoCollection->insert($data, $options));
+ Yii::endProfile($token, __METHOD__);
+
+ return is_array($data) ? $data['_id'] : $data->_id;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Inserts several new rows into collection.
+ * @param array $rows array of arrays or objects to be inserted.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return array inserted data, each row will have "_id" key assigned to it.
+ * @throws Exception on failure.
+ */
+ public function batchInsert($rows, $options = [])
+ {
+ $token = $this->composeLogToken('batchInsert', [$rows]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $options = array_merge(['w' => 1], $options);
+ $this->tryResultError($this->mongoCollection->batchInsert($rows, $options));
+ Yii::endProfile($token, __METHOD__);
+
+ return $rows;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Updates the rows, which matches given criteria by given data.
+ * Note: for "multiple" mode Mongo requires explicit strategy "$set" or "$inc"
+ * to be specified for the "newData". If no strategy is passed "$set" will be used.
+ * @param array $condition description of the objects to update.
+ * @param array $newData the object with which to update the matching records.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return integer|boolean number of updated documents or whether operation was successful.
+ * @throws Exception on failure.
+ */
+ public function update($condition, $newData, $options = [])
+ {
+ $condition = $this->buildCondition($condition);
+ $options = array_merge(['w' => 1, 'multiple' => true], $options);
+ if ($options['multiple']) {
+ $keys = array_keys($newData);
+ if (!empty($keys) && strncmp('$', $keys[0], 1) !== 0) {
+ $newData = ['$set' => $newData];
+ }
+ }
+ $token = $this->composeLogToken('update', [$condition, $newData, $options]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->update($condition, $newData, $options);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+ if (is_array($result) && array_key_exists('n', $result)) {
+ return $result['n'];
+ } else {
+ return true;
+ }
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Update the existing database data, otherwise insert this data
+ * @param array|object $data data to be updated/inserted.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return \MongoId updated/new record id instance.
+ * @throws Exception on failure.
+ */
+ public function save($data, $options = [])
+ {
+ $token = $this->composeLogToken('save', [$data]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $options = array_merge(['w' => 1], $options);
+ $this->tryResultError($this->mongoCollection->save($data, $options));
+ Yii::endProfile($token, __METHOD__);
+
+ return is_array($data) ? $data['_id'] : $data->_id;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Removes data from the collection.
+ * @param array $condition description of records to remove.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return integer|boolean number of updated documents or whether operation was successful.
+ * @throws Exception on failure.
+ * @see http://www.php.net/manual/en/mongocollection.remove.php
+ */
+ public function remove($condition = [], $options = [])
+ {
+ $condition = $this->buildCondition($condition);
+ $options = array_merge(['w' => 1, 'justOne' => false], $options);
+ $token = $this->composeLogToken('remove', [$condition, $options]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->remove($condition, $options);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+ if (is_array($result) && array_key_exists('n', $result)) {
+ return $result['n'];
+ } else {
+ return true;
+ }
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns a list of distinct values for the given column across a collection.
+ * @param string $column column to use.
+ * @param array $condition query parameters.
+ * @return array|boolean array of distinct values, or "false" on failure.
+ * @throws Exception on failure.
+ */
+ public function distinct($column, $condition = [])
+ {
+ $condition = $this->buildCondition($condition);
+ $token = $this->composeLogToken('distinct', [$column, $condition]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->distinct($column, $condition);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Performs aggregation using Mongo Aggregation Framework.
+ * @param array $pipeline list of pipeline operators, or just the first operator
+ * @param array $pipelineOperator additional pipeline operator. You can specify additional
+ * pipelines via third argument, fourth argument etc.
+ * @return array the result of the aggregation.
+ * @throws Exception on failure.
+ * @see http://docs.mongodb.org/manual/applications/aggregation/
+ */
+ public function aggregate($pipeline, $pipelineOperator = [])
+ {
+ $args = func_get_args();
+ $token = $this->composeLogToken('aggregate', $args);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = call_user_func_array([$this->mongoCollection, 'aggregate'], $args);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result['result'];
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Performs aggregation using Mongo "group" command.
+ * @param mixed $keys fields to group by. If an array or non-code object is passed,
+ * it will be the key used to group results. If instance of [[\MongoCode]] passed,
+ * it will be treated as a function that returns the key to group by.
+ * @param array $initial Initial value of the aggregation counter object.
+ * @param \MongoCode|string $reduce function that takes two arguments (the current
+ * document and the aggregation to this point) and does the aggregation.
+ * Argument will be automatically cast to [[\MongoCode]].
+ * @param array $options optional parameters to the group command. Valid options include:
+ * - condition - criteria for including a document in the aggregation.
+ * - finalize - function called once per unique key that takes the final output of the reduce function.
+ * @return array the result of the aggregation.
+ * @throws Exception on failure.
+ * @see http://docs.mongodb.org/manual/reference/command/group/
+ */
+ public function group($keys, $initial, $reduce, $options = [])
+ {
+ if (!($reduce instanceof \MongoCode)) {
+ $reduce = new \MongoCode((string) $reduce);
+ }
+ if (array_key_exists('condition', $options)) {
+ $options['condition'] = $this->buildCondition($options['condition']);
+ }
+ if (array_key_exists('finalize', $options)) {
+ if (!($options['finalize'] instanceof \MongoCode)) {
+ $options['finalize'] = new \MongoCode((string) $options['finalize']);
+ }
+ }
+ $token = $this->composeLogToken('group', [$keys, $initial, $reduce, $options]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ // Avoid possible E_DEPRECATED for $options:
+ if (empty($options)) {
+ $result = $this->mongoCollection->group($keys, $initial, $reduce);
+ } else {
+ $result = $this->mongoCollection->group($keys, $initial, $reduce, $options);
+ }
+ $this->tryResultError($result);
+
+ Yii::endProfile($token, __METHOD__);
+ if (array_key_exists('retval', $result)) {
+ return $result['retval'];
+ } else {
+ return [];
+ }
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Performs aggregation using Mongo "map reduce" mechanism.
+ * Note: this function will not return the aggregation result, instead it will
+ * write it inside the another Mongo collection specified by "out" parameter.
+ * For example:
+ *
+ * ~~~
+ * $customerCollection = Yii::$app->mongo->getCollection('customer');
+ * $resultCollectionName = $customerCollection->mapReduce(
+ * 'function () {emit(this.status, this.amount)}',
+ * 'function (key, values) {return Array.sum(values)}',
+ * 'mapReduceOut',
+ * ['status' => 3]
+ * );
+ * $query = new Query();
+ * $results = $query->from($resultCollectionName)->all();
+ * ~~~
+ *
+ * @param \MongoCode|string $map function, which emits map data from collection.
+ * Argument will be automatically cast to [[\MongoCode]].
+ * @param \MongoCode|string $reduce function that takes two arguments (the map key
+ * and the map values) and does the aggregation.
+ * Argument will be automatically cast to [[\MongoCode]].
+ * @param string|array $out output collection name. It could be a string for simple output
+ * ('outputCollection'), or an array for parametrized output (['merge' => 'outputCollection']).
+ * You can pass ['inline' => true] to fetch the result at once without temporary collection usage.
+ * @param array $condition criteria for including a document in the aggregation.
+ * @param array $options additional optional parameters to the mapReduce command. Valid options include:
+ * - sort - array - key to sort the input documents. The sort key must be in an existing index for this collection.
+ * - limit - the maximum number of documents to return in the collection.
+ * - finalize - function, which follows the reduce method and modifies the output.
+ * - scope - array - specifies global variables that are accessible in the map, reduce and finalize functions.
+ * - jsMode - boolean -Specifies whether to convert intermediate data into BSON format between the execution of the map and reduce functions.
+ * - verbose - boolean - specifies whether to include the timing information in the result information.
+ * @return string|array the map reduce output collection name or output results.
+ * @throws Exception on failure.
+ */
+ public function mapReduce($map, $reduce, $out, $condition = [], $options = [])
+ {
+ if (!($map instanceof \MongoCode)) {
+ $map = new \MongoCode((string) $map);
+ }
+ if (!($reduce instanceof \MongoCode)) {
+ $reduce = new \MongoCode((string) $reduce);
+ }
+ $command = [
+ 'mapReduce' => $this->getName(),
+ 'map' => $map,
+ 'reduce' => $reduce,
+ 'out' => $out
+ ];
+ if (!empty($condition)) {
+ $command['query'] = $this->buildCondition($condition);
+ }
+ if (array_key_exists('finalize', $options)) {
+ if (!($options['finalize'] instanceof \MongoCode)) {
+ $options['finalize'] = new \MongoCode((string) $options['finalize']);
+ }
+ }
+ if (!empty($options)) {
+ $command = array_merge($command, $options);
+ }
+ $token = $this->composeLogToken('mapReduce', [$map, $reduce, $out]);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $command = array_merge(['mapReduce' => $this->getName()], $command);
+ $result = $this->mongoCollection->db->command($command);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+
+ return array_key_exists('results', $result) ? $result['results'] : $result['result'];
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Performs full text search.
+ * @param string $search string of terms that MongoDB parses and uses to query the text index.
+ * @param array $condition criteria for filtering a results list.
+ * @param array $fields list of fields to be returned in result.
+ * @param array $options additional optional parameters to the mapReduce command. Valid options include:
+ * - limit - the maximum number of documents to include in the response (by default 100).
+ * - language - the language that determines the list of stop words for the search
+ * and the rules for the stemmer and tokenizer. If not specified, the search uses the default
+ * language of the index.
+ * @return array the highest scoring documents, in descending order by score.
+ * @throws Exception on failure.
+ */
+ public function fullTextSearch($search, $condition = [], $fields = [], $options = [])
+ {
+ $command = [
+ 'search' => $search
+ ];
+ if (!empty($condition)) {
+ $command['filter'] = $this->buildCondition($condition);
+ }
+ if (!empty($fields)) {
+ $command['project'] = $fields;
+ }
+ if (!empty($options)) {
+ $command = array_merge($command, $options);
+ }
+ $token = $this->composeLogToken('text', $command);
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $command = array_merge(['text' => $this->getName()], $command);
+ $result = $this->mongoCollection->db->command($command);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result['results'];
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Checks if command execution result ended with an error.
+ * @param mixed $result raw command execution result.
+ * @throws Exception if an error occurred.
+ */
+ protected function tryResultError($result)
+ {
+ if (is_array($result)) {
+ if (!empty($result['errmsg'])) {
+ $errorMessage = $result['errmsg'];
+ } elseif (!empty($result['err'])) {
+ $errorMessage = $result['err'];
+ }
+ if (isset($errorMessage)) {
+ if (array_key_exists('code', $result)) {
+ $errorCode = (int) $result['code'];
+ } elseif (array_key_exists('ok', $result)) {
+ $errorCode = (int) $result['ok'];
+ } else {
+ $errorCode = 0;
+ }
+ throw new Exception($errorMessage, $errorCode);
+ }
+ } elseif (!$result) {
+ throw new Exception('Unknown error, use "w=1" option to enable error tracking');
+ }
+ }
+
+ /**
+ * Throws an exception if there was an error on the last operation.
+ * @throws Exception if an error occurred.
+ */
+ protected function tryLastError()
+ {
+ $this->tryResultError($this->getLastError());
+ }
+
+ /**
+ * Converts "\yii\db\*" quick condition keyword into actual Mongo condition keyword.
+ * @param string $key raw condition key.
+ * @return string actual key.
+ */
+ protected function normalizeConditionKeyword($key)
+ {
+ static $map = [
+ 'OR' => '$or',
+ 'IN' => '$in',
+ 'NOT IN' => '$nin',
+ ];
+ $matchKey = strtoupper($key);
+ if (array_key_exists($matchKey, $map)) {
+ return $map[$matchKey];
+ } else {
+ return $key;
+ }
+ }
+
+ /**
+ * Converts given value into [[MongoId]] instance.
+ * If array given, each element of it will be processed.
+ * @param mixed $rawId raw id(s).
+ * @return array|\MongoId normalized id(s).
+ */
+ protected function ensureMongoId($rawId)
+ {
+ if (is_array($rawId)) {
+ $result = [];
+ foreach ($rawId as $key => $value) {
+ $result[$key] = $this->ensureMongoId($value);
+ }
+
+ return $result;
+ } elseif (is_object($rawId)) {
+ if ($rawId instanceof \MongoId) {
+ return $rawId;
+ } else {
+ $rawId = (string) $rawId;
+ }
+ }
+ try {
+ $mongoId = new \MongoId($rawId);
+ } catch (\MongoException $e) {
+ // invalid id format
+ $mongoId = $rawId;
+ }
+
+ return $mongoId;
+ }
+
+ /**
+ * Parses the condition specification and generates the corresponding Mongo condition.
+ * @param array $condition the condition specification. Please refer to [[Query::where()]]
+ * on how to specify a condition.
+ * @return array the generated Mongo condition
+ * @throws InvalidParamException if the condition is in bad format
+ */
+ public function buildCondition($condition)
+ {
+ static $builders = [
+ 'AND' => 'buildAndCondition',
+ 'OR' => 'buildOrCondition',
+ 'BETWEEN' => 'buildBetweenCondition',
+ 'NOT BETWEEN' => 'buildBetweenCondition',
+ 'IN' => 'buildInCondition',
+ 'NOT IN' => 'buildInCondition',
+ 'LIKE' => 'buildLikeCondition',
+ ];
+
+ if (!is_array($condition)) {
+ throw new InvalidParamException('Condition should be an array.');
+ } elseif (empty($condition)) {
+ return [];
+ }
+ if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
+ $operator = strtoupper($condition[0]);
+ if (isset($builders[$operator])) {
+ $method = $builders[$operator];
+ array_shift($condition);
+
+ return $this->$method($operator, $condition);
+ } else {
+ throw new InvalidParamException('Found unknown operator in query: ' . $operator);
+ }
+ } else {
+ // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
+ return $this->buildHashCondition($condition);
+ }
+ }
+
+ /**
+ * Creates a condition based on column-value pairs.
+ * @param array $condition the condition specification.
+ * @return array the generated Mongo condition.
+ */
+ public function buildHashCondition($condition)
+ {
+ $result = [];
+ foreach ($condition as $name => $value) {
+ if (strncmp('$', $name, 1) === 0) {
+ // Native Mongo condition:
+ $result[$name] = $value;
+ } else {
+ if (is_array($value)) {
+ if (array_key_exists(0, $value)) {
+ // Quick IN condition:
+ $result = array_merge($result, $this->buildInCondition('IN', [$name, $value]));
+ } else {
+ // Mongo complex condition:
+ $result[$name] = $value;
+ }
+ } else {
+ // Direct match:
+ if ($name == '_id') {
+ $value = $this->ensureMongoId($value);
+ }
+ $result[$name] = $value;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Connects two or more conditions with the `AND` operator.
+ * @param string $operator the operator to use for connecting the given operands
+ * @param array $operands the Mongo conditions to connect.
+ * @return array the generated Mongo condition.
+ */
+ public function buildAndCondition($operator, $operands)
+ {
+ $result = [];
+ foreach ($operands as $operand) {
+ $condition = $this->buildCondition($operand);
+ $result = array_merge_recursive($result, $condition);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Connects two or more conditions with the `OR` operator.
+ * @param string $operator the operator to use for connecting the given operands
+ * @param array $operands the Mongo conditions to connect.
+ * @return array the generated Mongo condition.
+ */
+ public function buildOrCondition($operator, $operands)
+ {
+ $operator = $this->normalizeConditionKeyword($operator);
+ $parts = [];
+ foreach ($operands as $operand) {
+ $parts[] = $this->buildCondition($operand);
+ }
+
+ return [$operator => $parts];
+ }
+
+ /**
+ * Creates an Mongo condition, which emulates the `BETWEEN` operator.
+ * @param string $operator the operator to use
+ * @param array $operands the first operand is the column name. The second and third operands
+ * describe the interval that column value should be in.
+ * @return array the generated Mongo condition.
+ * @throws InvalidParamException if wrong number of operands have been given.
+ */
+ public function buildBetweenCondition($operator, $operands)
+ {
+ if (!isset($operands[0], $operands[1], $operands[2])) {
+ throw new InvalidParamException("Operator '$operator' requires three operands.");
+ }
+ list($column, $value1, $value2) = $operands;
+ if (strncmp('NOT', $operator, 3) === 0) {
+ return [
+ $column => [
+ '$lt' => $value1,
+ '$gt' => $value2,
+ ]
+ ];
+ } else {
+ return [
+ $column => [
+ '$gte' => $value1,
+ '$lte' => $value2,
+ ]
+ ];
+ }
+ }
+
+ /**
+ * Creates an Mongo condition with the `IN` operator.
+ * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
+ * @param array $operands the first operand is the column name. If it is an array
+ * a composite IN condition will be generated.
+ * The second operand is an array of values that column value should be among.
+ * @return array the generated Mongo condition.
+ * @throws InvalidParamException if wrong number of operands have been given.
+ */
+ public function buildInCondition($operator, $operands)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new InvalidParamException("Operator '$operator' requires two operands.");
+ }
+
+ list($column, $values) = $operands;
+
+ $values = (array) $values;
+
+ if (!is_array($column)) {
+ $columns = [$column];
+ $values = [$column => $values];
+ } elseif (count($column) < 2) {
+ $columns = $column;
+ $values = [$column[0] => $values];
+ } else {
+ $columns = $column;
+ }
+
+ $operator = $this->normalizeConditionKeyword($operator);
+ $result = [];
+ foreach ($columns as $column) {
+ if ($column == '_id') {
+ $inValues = $this->ensureMongoId($values[$column]);
+ } else {
+ $inValues = $values[$column];
+ }
+ $result[$column][$operator] = $inValues;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Creates a Mongo condition, which emulates the `LIKE` operator.
+ * @param string $operator the operator to use
+ * @param array $operands the first operand is the column name.
+ * The second operand is a single value that column value should be compared with.
+ * @return array the generated Mongo condition.
+ * @throws InvalidParamException if wrong number of operands have been given.
+ */
+ public function buildLikeCondition($operator, $operands)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new InvalidParamException("Operator '$operator' requires two operands.");
+ }
+ list($column, $value) = $operands;
+ if (!($value instanceof \MongoRegex)) {
+ $value = new \MongoRegex($value);
+ }
+
+ return [$column => $value];
+ }
}
diff --git a/extensions/mongodb/Connection.php b/extensions/mongodb/Connection.php
index 11a0c1a4db5..4d1aef9becc 100644
--- a/extensions/mongodb/Connection.php
+++ b/extensions/mongodb/Connection.php
@@ -72,201 +72,206 @@
*/
class Connection extends Component
{
- /**
- * @event Event an event that is triggered after a DB connection is established
- */
- const EVENT_AFTER_OPEN = 'afterOpen';
+ /**
+ * @event Event an event that is triggered after a DB connection is established
+ */
+ const EVENT_AFTER_OPEN = 'afterOpen';
- /**
- * @var string host:port
- *
- * Correct syntax is:
- * mongodb://[username:password@]host1[:port1][,host2[:port2:],...][/dbname]
- * For example:
- * mongodb://localhost:27017
- * mongodb://developer:password@localhost:27017
- * mongodb://developer:password@localhost:27017/mydatabase
- */
- public $dsn;
- /**
- * @var array connection options.
- * for example:
- *
- * ~~~
- * [
- * 'socketTimeoutMS' => 1000, // how long a send or receive on a socket can take before timing out
- * 'journal' => true // block write operations until the journal be flushed the to disk
- * ]
- * ~~~
- *
- * @see http://www.php.net/manual/en/mongoclient.construct.php
- */
- public $options = [];
- /**
- * @var string name of the Mongo database to use by default.
- * If this field left blank, connection instance will attempt to determine it from
- * [[options]] and [[dsn]] automatically, if needed.
- */
- public $defaultDatabaseName;
- /**
- * @var \MongoClient Mongo client instance.
- */
- public $mongoClient;
- /**
- * @var Database[] list of Mongo databases
- */
- private $_databases = [];
+ /**
+ * @var string host:port
+ *
+ * Correct syntax is:
+ * mongodb://[username:password@]host1[:port1][,host2[:port2:],...][/dbname]
+ * For example:
+ * mongodb://localhost:27017
+ * mongodb://developer:password@localhost:27017
+ * mongodb://developer:password@localhost:27017/mydatabase
+ */
+ public $dsn;
+ /**
+ * @var array connection options.
+ * for example:
+ *
+ * ~~~
+ * [
+ * 'socketTimeoutMS' => 1000, // how long a send or receive on a socket can take before timing out
+ * 'journal' => true // block write operations until the journal be flushed the to disk
+ * ]
+ * ~~~
+ *
+ * @see http://www.php.net/manual/en/mongoclient.construct.php
+ */
+ public $options = [];
+ /**
+ * @var string name of the Mongo database to use by default.
+ * If this field left blank, connection instance will attempt to determine it from
+ * [[options]] and [[dsn]] automatically, if needed.
+ */
+ public $defaultDatabaseName;
+ /**
+ * @var \MongoClient Mongo client instance.
+ */
+ public $mongoClient;
+ /**
+ * @var Database[] list of Mongo databases
+ */
+ private $_databases = [];
- /**
- * Returns the Mongo collection with the given name.
- * @param string|null $name collection name, if null default one will be used.
- * @param boolean $refresh whether to reestablish the database connection even if it is found in the cache.
- * @return Database database instance.
- */
- public function getDatabase($name = null, $refresh = false)
- {
- if ($name === null) {
- $name = $this->fetchDefaultDatabaseName();
- }
- if ($refresh || !array_key_exists($name, $this->_databases)) {
- $this->_databases[$name] = $this->selectDatabase($name);
- }
- return $this->_databases[$name];
- }
+ /**
+ * Returns the Mongo collection with the given name.
+ * @param string|null $name collection name, if null default one will be used.
+ * @param boolean $refresh whether to reestablish the database connection even if it is found in the cache.
+ * @return Database database instance.
+ */
+ public function getDatabase($name = null, $refresh = false)
+ {
+ if ($name === null) {
+ $name = $this->fetchDefaultDatabaseName();
+ }
+ if ($refresh || !array_key_exists($name, $this->_databases)) {
+ $this->_databases[$name] = $this->selectDatabase($name);
+ }
- /**
- * Returns [[defaultDatabaseName]] value, if it is not set,
- * attempts to determine it from [[dsn]] value.
- * @return string default database name
- * @throws \yii\base\InvalidConfigException if unable to determine default database name.
- */
- protected function fetchDefaultDatabaseName()
- {
- if ($this->defaultDatabaseName === null) {
- if (isset($this->options['db'])) {
- $this->defaultDatabaseName = $this->options['db'];
- } elseif (preg_match('/^mongodb:\\/\\/.+\\/(.+)$/s', $this->dsn, $matches)) {
- $this->defaultDatabaseName = $matches[1];
- } else {
- throw new InvalidConfigException("Unable to determine default database name from dsn.");
- }
- }
- return $this->defaultDatabaseName;
- }
+ return $this->_databases[$name];
+ }
- /**
- * Selects the database with given name.
- * @param string $name database name.
- * @return Database database instance.
- */
- protected function selectDatabase($name)
- {
- $this->open();
- return Yii::createObject([
- 'class' => 'yii\mongodb\Database',
- 'mongoDb' => $this->mongoClient->selectDB($name)
- ]);
- }
+ /**
+ * Returns [[defaultDatabaseName]] value, if it is not set,
+ * attempts to determine it from [[dsn]] value.
+ * @return string default database name
+ * @throws \yii\base\InvalidConfigException if unable to determine default database name.
+ */
+ protected function fetchDefaultDatabaseName()
+ {
+ if ($this->defaultDatabaseName === null) {
+ if (isset($this->options['db'])) {
+ $this->defaultDatabaseName = $this->options['db'];
+ } elseif (preg_match('/^mongodb:\\/\\/.+\\/(.+)$/s', $this->dsn, $matches)) {
+ $this->defaultDatabaseName = $matches[1];
+ } else {
+ throw new InvalidConfigException("Unable to determine default database name from dsn.");
+ }
+ }
- /**
- * Returns the Mongo collection with the given name.
- * @param string|array $name collection name. If string considered as the name of the collection
- * inside the default database. If array - first element considered as the name of the database,
- * second - as name of collection inside that database
- * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
- * @return Collection Mongo collection instance.
- */
- public function getCollection($name, $refresh = false)
- {
- if (is_array($name)) {
- list ($dbName, $collectionName) = $name;
- return $this->getDatabase($dbName)->getCollection($collectionName, $refresh);
- } else {
- return $this->getDatabase()->getCollection($name, $refresh);
- }
- }
+ return $this->defaultDatabaseName;
+ }
- /**
- * Returns the Mongo GridFS collection.
- * @param string|array $prefix collection prefix. If string considered as the prefix of the GridFS
- * collection inside the default database. If array - first element considered as the name of the database,
- * second - as prefix of the GridFS collection inside that database, if no second element present
- * default "fs" prefix will be used.
- * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
- * @return file\Collection Mongo GridFS collection instance.
- */
- public function getFileCollection($prefix = 'fs', $refresh = false)
- {
- if (is_array($prefix)) {
- list ($dbName, $collectionPrefix) = $prefix;
- if (!isset($collectionPrefix)) {
- $collectionPrefix = 'fs';
- }
- return $this->getDatabase($dbName)->getFileCollection($collectionPrefix, $refresh);
- } else {
- return $this->getDatabase()->getFileCollection($prefix, $refresh);
- }
- }
+ /**
+ * Selects the database with given name.
+ * @param string $name database name.
+ * @return Database database instance.
+ */
+ protected function selectDatabase($name)
+ {
+ $this->open();
- /**
- * Returns a value indicating whether the Mongo connection is established.
- * @return boolean whether the Mongo connection is established
- */
- public function getIsActive()
- {
- return is_object($this->mongoClient) && $this->mongoClient->connected;
- }
+ return Yii::createObject([
+ 'class' => 'yii\mongodb\Database',
+ 'mongoDb' => $this->mongoClient->selectDB($name)
+ ]);
+ }
- /**
- * Establishes a Mongo connection.
- * It does nothing if a Mongo connection has already been established.
- * @throws Exception if connection fails
- */
- public function open()
- {
- if ($this->mongoClient === null) {
- if (empty($this->dsn)) {
- throw new InvalidConfigException($this->className() . '::dsn cannot be empty.');
- }
- $token = 'Opening MongoDB connection: ' . $this->dsn;
- try {
- Yii::trace($token, __METHOD__);
- Yii::beginProfile($token, __METHOD__);
- $options = $this->options;
- $options['connect'] = true;
- if ($this->defaultDatabaseName !== null) {
- $options['db'] = $this->defaultDatabaseName;
- }
- $this->mongoClient = new \MongoClient($this->dsn, $options);
- $this->initConnection();
- Yii::endProfile($token, __METHOD__);
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
- }
+ /**
+ * Returns the Mongo collection with the given name.
+ * @param string|array $name collection name. If string considered as the name of the collection
+ * inside the default database. If array - first element considered as the name of the database,
+ * second - as name of collection inside that database
+ * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
+ * @return Collection Mongo collection instance.
+ */
+ public function getCollection($name, $refresh = false)
+ {
+ if (is_array($name)) {
+ list ($dbName, $collectionName) = $name;
- /**
- * Closes the currently active DB connection.
- * It does nothing if the connection is already closed.
- */
- public function close()
- {
- if ($this->mongoClient !== null) {
- Yii::trace('Closing MongoDB connection: ' . $this->dsn, __METHOD__);
- $this->mongoClient = null;
- $this->_databases = [];
- }
- }
+ return $this->getDatabase($dbName)->getCollection($collectionName, $refresh);
+ } else {
+ return $this->getDatabase()->getCollection($name, $refresh);
+ }
+ }
- /**
- * Initializes the DB connection.
- * This method is invoked right after the DB connection is established.
- * The default implementation triggers an [[EVENT_AFTER_OPEN]] event.
- */
- protected function initConnection()
- {
- $this->trigger(self::EVENT_AFTER_OPEN);
- }
+ /**
+ * Returns the Mongo GridFS collection.
+ * @param string|array $prefix collection prefix. If string considered as the prefix of the GridFS
+ * collection inside the default database. If array - first element considered as the name of the database,
+ * second - as prefix of the GridFS collection inside that database, if no second element present
+ * default "fs" prefix will be used.
+ * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
+ * @return file\Collection Mongo GridFS collection instance.
+ */
+ public function getFileCollection($prefix = 'fs', $refresh = false)
+ {
+ if (is_array($prefix)) {
+ list ($dbName, $collectionPrefix) = $prefix;
+ if (!isset($collectionPrefix)) {
+ $collectionPrefix = 'fs';
+ }
+
+ return $this->getDatabase($dbName)->getFileCollection($collectionPrefix, $refresh);
+ } else {
+ return $this->getDatabase()->getFileCollection($prefix, $refresh);
+ }
+ }
+
+ /**
+ * Returns a value indicating whether the Mongo connection is established.
+ * @return boolean whether the Mongo connection is established
+ */
+ public function getIsActive()
+ {
+ return is_object($this->mongoClient) && $this->mongoClient->connected;
+ }
+
+ /**
+ * Establishes a Mongo connection.
+ * It does nothing if a Mongo connection has already been established.
+ * @throws Exception if connection fails
+ */
+ public function open()
+ {
+ if ($this->mongoClient === null) {
+ if (empty($this->dsn)) {
+ throw new InvalidConfigException($this->className() . '::dsn cannot be empty.');
+ }
+ $token = 'Opening MongoDB connection: ' . $this->dsn;
+ try {
+ Yii::trace($token, __METHOD__);
+ Yii::beginProfile($token, __METHOD__);
+ $options = $this->options;
+ $options['connect'] = true;
+ if ($this->defaultDatabaseName !== null) {
+ $options['db'] = $this->defaultDatabaseName;
+ }
+ $this->mongoClient = new \MongoClient($this->dsn, $options);
+ $this->initConnection();
+ Yii::endProfile($token, __METHOD__);
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Closes the currently active DB connection.
+ * It does nothing if the connection is already closed.
+ */
+ public function close()
+ {
+ if ($this->mongoClient !== null) {
+ Yii::trace('Closing MongoDB connection: ' . $this->dsn, __METHOD__);
+ $this->mongoClient = null;
+ $this->_databases = [];
+ }
+ }
+
+ /**
+ * Initializes the DB connection.
+ * This method is invoked right after the DB connection is established.
+ * The default implementation triggers an [[EVENT_AFTER_OPEN]] event.
+ */
+ protected function initConnection()
+ {
+ $this->trigger(self::EVENT_AFTER_OPEN);
+ }
}
diff --git a/extensions/mongodb/Database.php b/extensions/mongodb/Database.php
index 2ded6b376a0..a560dbf57c0 100644
--- a/extensions/mongodb/Database.php
+++ b/extensions/mongodb/Database.php
@@ -22,152 +22,156 @@
*/
class Database extends Object
{
- /**
- * @var \MongoDB Mongo database instance.
- */
- public $mongoDb;
- /**
- * @var Collection[] list of collections.
- */
- private $_collections = [];
- /**
- * @var file\Collection[] list of GridFS collections.
- */
- private $_fileCollections = [];
+ /**
+ * @var \MongoDB Mongo database instance.
+ */
+ public $mongoDb;
+ /**
+ * @var Collection[] list of collections.
+ */
+ private $_collections = [];
+ /**
+ * @var file\Collection[] list of GridFS collections.
+ */
+ private $_fileCollections = [];
- /**
- * @return string name of this database.
- */
- public function getName()
- {
- return $this->mongoDb->__toString();
- }
+ /**
+ * @return string name of this database.
+ */
+ public function getName()
+ {
+ return $this->mongoDb->__toString();
+ }
- /**
- * Returns the Mongo collection with the given name.
- * @param string $name collection name
- * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
- * @return Collection Mongo collection instance.
- */
- public function getCollection($name, $refresh = false)
- {
- if ($refresh || !array_key_exists($name, $this->_collections)) {
- $this->_collections[$name] = $this->selectCollection($name);
- }
- return $this->_collections[$name];
- }
+ /**
+ * Returns the Mongo collection with the given name.
+ * @param string $name collection name
+ * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
+ * @return Collection Mongo collection instance.
+ */
+ public function getCollection($name, $refresh = false)
+ {
+ if ($refresh || !array_key_exists($name, $this->_collections)) {
+ $this->_collections[$name] = $this->selectCollection($name);
+ }
- /**
- * Returns Mongo GridFS collection with given prefix.
- * @param string $prefix collection prefix.
- * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
- * @return file\Collection Mongo GridFS collection.
- */
- public function getFileCollection($prefix = 'fs', $refresh = false)
- {
- if ($refresh || !array_key_exists($prefix, $this->_fileCollections)) {
- $this->_fileCollections[$prefix] = $this->selectFileCollection($prefix);
- }
- return $this->_fileCollections[$prefix];
- }
+ return $this->_collections[$name];
+ }
- /**
- * Selects collection with given name.
- * @param string $name collection name.
- * @return Collection collection instance.
- */
- protected function selectCollection($name)
- {
- return Yii::createObject([
- 'class' => 'yii\mongodb\Collection',
- 'mongoCollection' => $this->mongoDb->selectCollection($name)
- ]);
- }
+ /**
+ * Returns Mongo GridFS collection with given prefix.
+ * @param string $prefix collection prefix.
+ * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
+ * @return file\Collection Mongo GridFS collection.
+ */
+ public function getFileCollection($prefix = 'fs', $refresh = false)
+ {
+ if ($refresh || !array_key_exists($prefix, $this->_fileCollections)) {
+ $this->_fileCollections[$prefix] = $this->selectFileCollection($prefix);
+ }
- /**
- * Selects GridFS collection with given prefix.
- * @param string $prefix file collection prefix.
- * @return file\Collection file collection instance.
- */
- protected function selectFileCollection($prefix)
- {
- return Yii::createObject([
- 'class' => 'yii\mongodb\file\Collection',
- 'mongoCollection' => $this->mongoDb->getGridFS($prefix)
- ]);
- }
+ return $this->_fileCollections[$prefix];
+ }
- /**
- * Creates new collection.
- * Note: Mongo creates new collections automatically on the first demand,
- * this method makes sense only for the migration script or for the case
- * you need to create collection with the specific options.
- * @param string $name name of the collection
- * @param array $options collection options in format: "name" => "value"
- * @return \MongoCollection new Mongo collection instance.
- * @throws Exception on failure.
- */
- public function createCollection($name, $options = [])
- {
- $token = $this->getName() . '.create(' . $name . ', ' . Json::encode($options) . ')';
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoDb->createCollection($name, $options);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
+ /**
+ * Selects collection with given name.
+ * @param string $name collection name.
+ * @return Collection collection instance.
+ */
+ protected function selectCollection($name)
+ {
+ return Yii::createObject([
+ 'class' => 'yii\mongodb\Collection',
+ 'mongoCollection' => $this->mongoDb->selectCollection($name)
+ ]);
+ }
- /**
- * Executes Mongo command.
- * @param array $command command specification.
- * @param array $options options in format: "name" => "value"
- * @return array database response.
- * @throws Exception on failure.
- */
- public function executeCommand($command, $options = [])
- {
- $token = $this->getName() . '.$cmd(' . Json::encode($command) . ', ' . Json::encode($options) . ')';
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoDb->command($command, $options);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
+ /**
+ * Selects GridFS collection with given prefix.
+ * @param string $prefix file collection prefix.
+ * @return file\Collection file collection instance.
+ */
+ protected function selectFileCollection($prefix)
+ {
+ return Yii::createObject([
+ 'class' => 'yii\mongodb\file\Collection',
+ 'mongoCollection' => $this->mongoDb->getGridFS($prefix)
+ ]);
+ }
- /**
- * Checks if command execution result ended with an error.
- * @param mixed $result raw command execution result.
- * @throws Exception if an error occurred.
- */
- protected function tryResultError($result)
- {
- if (is_array($result)) {
- if (!empty($result['errmsg'])) {
- $errorMessage = $result['errmsg'];
- } elseif (!empty($result['err'])) {
- $errorMessage = $result['err'];
- }
- if (isset($errorMessage)) {
- if (array_key_exists('ok', $result)) {
- $errorCode = (int)$result['ok'];
- } else {
- $errorCode = 0;
- }
- throw new Exception($errorMessage, $errorCode);
- }
- } elseif (!$result) {
- throw new Exception('Unknown error, use "w=1" option to enable error tracking');
- }
- }
+ /**
+ * Creates new collection.
+ * Note: Mongo creates new collections automatically on the first demand,
+ * this method makes sense only for the migration script or for the case
+ * you need to create collection with the specific options.
+ * @param string $name name of the collection
+ * @param array $options collection options in format: "name" => "value"
+ * @return \MongoCollection new Mongo collection instance.
+ * @throws Exception on failure.
+ */
+ public function createCollection($name, $options = [])
+ {
+ $token = $this->getName() . '.create(' . $name . ', ' . Json::encode($options) . ')';
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoDb->createCollection($name, $options);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes Mongo command.
+ * @param array $command command specification.
+ * @param array $options options in format: "name" => "value"
+ * @return array database response.
+ * @throws Exception on failure.
+ */
+ public function executeCommand($command, $options = [])
+ {
+ $token = $this->getName() . '.$cmd(' . Json::encode($command) . ', ' . Json::encode($options) . ')';
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoDb->command($command, $options);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Checks if command execution result ended with an error.
+ * @param mixed $result raw command execution result.
+ * @throws Exception if an error occurred.
+ */
+ protected function tryResultError($result)
+ {
+ if (is_array($result)) {
+ if (!empty($result['errmsg'])) {
+ $errorMessage = $result['errmsg'];
+ } elseif (!empty($result['err'])) {
+ $errorMessage = $result['err'];
+ }
+ if (isset($errorMessage)) {
+ if (array_key_exists('ok', $result)) {
+ $errorCode = (int) $result['ok'];
+ } else {
+ $errorCode = 0;
+ }
+ throw new Exception($errorMessage, $errorCode);
+ }
+ } elseif (!$result) {
+ throw new Exception('Unknown error, use "w=1" option to enable error tracking');
+ }
+ }
}
diff --git a/extensions/mongodb/Exception.php b/extensions/mongodb/Exception.php
index a9385612b4e..3cc5ae48b2f 100644
--- a/extensions/mongodb/Exception.php
+++ b/extensions/mongodb/Exception.php
@@ -15,11 +15,11 @@
*/
class Exception extends \yii\base\Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'MongoDB Exception';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'MongoDB Exception';
+ }
}
diff --git a/extensions/mongodb/Query.php b/extensions/mongodb/Query.php
index 6594a5702bc..3058ea084c8 100644
--- a/extensions/mongodb/Query.php
+++ b/extensions/mongodb/Query.php
@@ -38,309 +38,318 @@
*/
class Query extends Component implements QueryInterface
{
- use QueryTrait;
+ use QueryTrait;
- /**
- * @var array the fields of the results to return. For example, `['name', 'group_id']`.
- * The "_id" field is always returned. If not set, if means selecting all columns.
- * @see select()
- */
- public $select = [];
- /**
- * @var string|array the collection to be selected from. If string considered as the name of the collection
- * inside the default database. If array - first element considered as the name of the database,
- * second - as name of collection inside that database
- * @see from()
- */
- public $from;
+ /**
+ * @var array the fields of the results to return. For example, `['name', 'group_id']`.
+ * The "_id" field is always returned. If not set, if means selecting all columns.
+ * @see select()
+ */
+ public $select = [];
+ /**
+ * @var string|array the collection to be selected from. If string considered as the name of the collection
+ * inside the default database. If array - first element considered as the name of the database,
+ * second - as name of collection inside that database
+ * @see from()
+ */
+ public $from;
- /**
- * Returns the Mongo collection for this query.
- * @param Connection $db Mongo connection.
- * @return Collection collection instance.
- */
- public function getCollection($db = null)
- {
- if ($db === null) {
- $db = Yii::$app->getComponent('mongodb');
- }
- return $db->getCollection($this->from);
- }
+ /**
+ * Returns the Mongo collection for this query.
+ * @param Connection $db Mongo connection.
+ * @return Collection collection instance.
+ */
+ public function getCollection($db = null)
+ {
+ if ($db === null) {
+ $db = Yii::$app->getComponent('mongodb');
+ }
- /**
- * Sets the list of fields of the results to return.
- * @param array $fields fields of the results to return.
- * @return static the query object itself.
- */
- public function select(array $fields)
- {
- $this->select = $fields;
- return $this;
- }
+ return $db->getCollection($this->from);
+ }
- /**
- * Sets the collection to be selected from.
- * @param string|array the collection to be selected from. If string considered as the name of the collection
- * inside the default database. If array - first element considered as the name of the database,
- * second - as name of collection inside that database
- * @return static the query object itself.
- */
- public function from($collection)
- {
- $this->from = $collection;
- return $this;
- }
+ /**
+ * Sets the list of fields of the results to return.
+ * @param array $fields fields of the results to return.
+ * @return static the query object itself.
+ */
+ public function select(array $fields)
+ {
+ $this->select = $fields;
- /**
- * Builds the Mongo cursor for this query.
- * @param Connection $db the database connection used to execute the query.
- * @return \MongoCursor mongo cursor instance.
- */
- protected function buildCursor($db = null)
- {
- if ($this->where === null) {
- $where = [];
- } else {
- $where = $this->where;
- }
- $selectFields = [];
- if (!empty($this->select)) {
- foreach ($this->select as $fieldName) {
- $selectFields[$fieldName] = true;
- }
- }
- $cursor = $this->getCollection($db)->find($where, $selectFields);
- if (!empty($this->orderBy)) {
- $sort = [];
- foreach ($this->orderBy as $fieldName => $sortOrder) {
- $sort[$fieldName] = $sortOrder === SORT_DESC ? \MongoCollection::DESCENDING : \MongoCollection::ASCENDING;
- }
- $cursor->sort($sort);
- }
- $cursor->limit($this->limit);
- $cursor->skip($this->offset);
- return $cursor;
- }
+ return $this;
+ }
- /**
- * Fetches rows from the given Mongo cursor.
- * @param \MongoCursor $cursor Mongo cursor instance to fetch data from.
- * @param boolean $all whether to fetch all rows or only first one.
- * @param string|callable $indexBy the column name or PHP callback,
- * by which the query results should be indexed by.
- * @throws Exception on failure.
- * @return array|boolean result.
- */
- protected function fetchRows($cursor, $all = true, $indexBy = null)
- {
- $token = 'find(' . Json::encode($cursor->info()) . ')';
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->fetchRowsInternal($cursor, $all, $indexBy);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
+ /**
+ * Sets the collection to be selected from.
+ * @param string|array the collection to be selected from. If string considered as the name of the collection
+ * inside the default database. If array - first element considered as the name of the database,
+ * second - as name of collection inside that database
+ * @return static the query object itself.
+ */
+ public function from($collection)
+ {
+ $this->from = $collection;
- /**
- * @param \MongoCursor $cursor Mongo cursor instance to fetch data from.
- * @param boolean $all whether to fetch all rows or only first one.
- * @param string|callable $indexBy value to index by.
- * @return array|boolean result.
- * @see Query::fetchRows()
- */
- protected function fetchRowsInternal($cursor, $all, $indexBy)
- {
- $result = [];
- if ($all) {
- foreach ($cursor as $row) {
- if ($indexBy !== null) {
- if (is_string($indexBy)) {
- $key = $row[$indexBy];
- } else {
- $key = call_user_func($indexBy, $row);
- }
- $result[$key] = $row;
- } else {
- $result[] = $row;
- }
- }
- } else {
- if ($cursor->hasNext()) {
- $result = $cursor->getNext();
- } else {
- $result = false;
- }
- }
- return $result;
- }
+ return $this;
+ }
- /**
- * Executes the query and returns all results as an array.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- $cursor = $this->buildCursor($db);
- return $this->fetchRows($cursor, true, $this->indexBy);
- }
+ /**
+ * Builds the Mongo cursor for this query.
+ * @param Connection $db the database connection used to execute the query.
+ * @return \MongoCursor mongo cursor instance.
+ */
+ protected function buildCursor($db = null)
+ {
+ if ($this->where === null) {
+ $where = [];
+ } else {
+ $where = $this->where;
+ }
+ $selectFields = [];
+ if (!empty($this->select)) {
+ foreach ($this->select as $fieldName) {
+ $selectFields[$fieldName] = true;
+ }
+ }
+ $cursor = $this->getCollection($db)->find($where, $selectFields);
+ if (!empty($this->orderBy)) {
+ $sort = [];
+ foreach ($this->orderBy as $fieldName => $sortOrder) {
+ $sort[$fieldName] = $sortOrder === SORT_DESC ? \MongoCollection::DESCENDING : \MongoCollection::ASCENDING;
+ }
+ $cursor->sort($sort);
+ }
+ $cursor->limit($this->limit);
+ $cursor->skip($this->offset);
- /**
- * Executes the query and returns a single row of result.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
- * results in nothing.
- */
- public function one($db = null)
- {
- $cursor = $this->buildCursor($db);
- return $this->fetchRows($cursor, false);
- }
+ return $cursor;
+ }
- /**
- * Returns the number of records.
- * @param string $q kept to match [[QueryInterface]], its value is ignored.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return integer number of records
- * @throws Exception on failure.
- */
- public function count($q = '*', $db = null)
- {
- $cursor = $this->buildCursor($db);
- $token = 'find.count(' . Json::encode($cursor->info()) . ')';
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $cursor->count();
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
+ /**
+ * Fetches rows from the given Mongo cursor.
+ * @param \MongoCursor $cursor Mongo cursor instance to fetch data from.
+ * @param boolean $all whether to fetch all rows or only first one.
+ * @param string|callable $indexBy the column name or PHP callback,
+ * by which the query results should be indexed by.
+ * @throws Exception on failure.
+ * @return array|boolean result.
+ */
+ protected function fetchRows($cursor, $all = true, $indexBy = null)
+ {
+ $token = 'find(' . Json::encode($cursor->info()) . ')';
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->fetchRowsInternal($cursor, $all, $indexBy);
+ Yii::endProfile($token, __METHOD__);
- /**
- * Returns a value indicating whether the query result contains any row of data.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return boolean whether the query result contains any row of data.
- */
- public function exists($db = null)
- {
- return $this->one($db) !== null;
- }
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
- /**
- * Returns the sum of the specified column values.
- * @param string $q the column name.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return integer the sum of the specified column values
- */
- public function sum($q, $db = null)
- {
- return $this->aggregate($q, 'sum', $db);
- }
+ /**
+ * @param \MongoCursor $cursor Mongo cursor instance to fetch data from.
+ * @param boolean $all whether to fetch all rows or only first one.
+ * @param string|callable $indexBy value to index by.
+ * @return array|boolean result.
+ * @see Query::fetchRows()
+ */
+ protected function fetchRowsInternal($cursor, $all, $indexBy)
+ {
+ $result = [];
+ if ($all) {
+ foreach ($cursor as $row) {
+ if ($indexBy !== null) {
+ if (is_string($indexBy)) {
+ $key = $row[$indexBy];
+ } else {
+ $key = call_user_func($indexBy, $row);
+ }
+ $result[$key] = $row;
+ } else {
+ $result[] = $row;
+ }
+ }
+ } else {
+ if ($cursor->hasNext()) {
+ $result = $cursor->getNext();
+ } else {
+ $result = false;
+ }
+ }
- /**
- * Returns the average of the specified column values.
- * @param string $q the column name.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return integer the average of the specified column values.
- */
- public function average($q, $db = null)
- {
- return $this->aggregate($q, 'avg', $db);
- }
+ return $result;
+ }
- /**
- * Returns the minimum of the specified column values.
- * @param string $q the column name.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the minimum of the specified column values.
- */
- public function min($q, $db = null)
- {
- return $this->aggregate($q, 'min', $db);
- }
+ /**
+ * Executes the query and returns all results as an array.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $cursor = $this->buildCursor($db);
- /**
- * Returns the maximum of the specified column values.
- * @param string $q the column name.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return integer the maximum of the specified column values.
- */
- public function max($q, $db = null)
- {
- return $this->aggregate($q, 'max', $db);
- }
+ return $this->fetchRows($cursor, true, $this->indexBy);
+ }
- /**
- * Performs the aggregation for the given column.
- * @param string $column column name.
- * @param string $operator aggregation operator.
- * @param Connection $db the database connection used to execute the query.
- * @return integer aggregation result.
- */
- protected function aggregate($column, $operator, $db)
- {
- $collection = $this->getCollection($db);
- $pipelines = [];
- if ($this->where !== null) {
- $pipelines[] = ['$match' => $collection->buildCondition($this->where)];
- }
- $pipelines[] = [
- '$group' => [
- '_id' => '1',
- 'total' => [
- '$' . $operator => '$' . $column
- ],
- ]
- ];
- $result = $collection->aggregate($pipelines);
- if (array_key_exists(0, $result)) {
- return $result[0]['total'];
- } else {
- return 0;
- }
- }
+ /**
+ * Executes the query and returns a single row of result.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ */
+ public function one($db = null)
+ {
+ $cursor = $this->buildCursor($db);
- /**
- * Returns a list of distinct values for the given column across a collection.
- * @param string $q column to use.
- * @param Connection $db the Mongo connection used to execute the query.
- * If this parameter is not given, the `mongodb` application component will be used.
- * @return array array of distinct values
- */
- public function distinct($q, $db = null)
- {
- $collection = $this->getCollection($db);
- if ($this->where !== null) {
- $condition = $this->where;
- } else {
- $condition = [];
- }
- $result = $collection->distinct($q, $condition);
- if ($result === false) {
- return [];
- } else {
- return $result;
- }
- }
+ return $this->fetchRows($cursor, false);
+ }
+
+ /**
+ * Returns the number of records.
+ * @param string $q kept to match [[QueryInterface]], its value is ignored.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return integer number of records
+ * @throws Exception on failure.
+ */
+ public function count($q = '*', $db = null)
+ {
+ $cursor = $this->buildCursor($db);
+ $token = 'find.count(' . Json::encode($cursor->info()) . ')';
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $cursor->count();
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns a value indicating whether the query result contains any row of data.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return boolean whether the query result contains any row of data.
+ */
+ public function exists($db = null)
+ {
+ return $this->one($db) !== null;
+ }
+
+ /**
+ * Returns the sum of the specified column values.
+ * @param string $q the column name.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return integer the sum of the specified column values
+ */
+ public function sum($q, $db = null)
+ {
+ return $this->aggregate($q, 'sum', $db);
+ }
+
+ /**
+ * Returns the average of the specified column values.
+ * @param string $q the column name.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return integer the average of the specified column values.
+ */
+ public function average($q, $db = null)
+ {
+ return $this->aggregate($q, 'avg', $db);
+ }
+
+ /**
+ * Returns the minimum of the specified column values.
+ * @param string $q the column name.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the minimum of the specified column values.
+ */
+ public function min($q, $db = null)
+ {
+ return $this->aggregate($q, 'min', $db);
+ }
+
+ /**
+ * Returns the maximum of the specified column values.
+ * @param string $q the column name.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return integer the maximum of the specified column values.
+ */
+ public function max($q, $db = null)
+ {
+ return $this->aggregate($q, 'max', $db);
+ }
+
+ /**
+ * Performs the aggregation for the given column.
+ * @param string $column column name.
+ * @param string $operator aggregation operator.
+ * @param Connection $db the database connection used to execute the query.
+ * @return integer aggregation result.
+ */
+ protected function aggregate($column, $operator, $db)
+ {
+ $collection = $this->getCollection($db);
+ $pipelines = [];
+ if ($this->where !== null) {
+ $pipelines[] = ['$match' => $collection->buildCondition($this->where)];
+ }
+ $pipelines[] = [
+ '$group' => [
+ '_id' => '1',
+ 'total' => [
+ '$' . $operator => '$' . $column
+ ],
+ ]
+ ];
+ $result = $collection->aggregate($pipelines);
+ if (array_key_exists(0, $result)) {
+ return $result[0]['total'];
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Returns a list of distinct values for the given column across a collection.
+ * @param string $q column to use.
+ * @param Connection $db the Mongo connection used to execute the query.
+ * If this parameter is not given, the `mongodb` application component will be used.
+ * @return array array of distinct values
+ */
+ public function distinct($q, $db = null)
+ {
+ $collection = $this->getCollection($db);
+ if ($this->where !== null) {
+ $condition = $this->where;
+ } else {
+ $condition = [];
+ }
+ $result = $collection->distinct($q, $condition);
+ if ($result === false) {
+ return [];
+ } else {
+ return $result;
+ }
+ }
}
diff --git a/extensions/mongodb/Session.php b/extensions/mongodb/Session.php
index efa78b6e494..a969f75eb69 100644
--- a/extensions/mongodb/Session.php
+++ b/extensions/mongodb/Session.php
@@ -35,156 +35,160 @@
*/
class Session extends \yii\web\Session
{
- /**
- * @var Connection|string the MongoDB connection object or the application component ID of the MongoDB connection.
- * After the Session object is created, if you want to change this property, you should only assign it
- * with a MongoDB connection object.
- */
- public $db = 'mongodb';
- /**
- * @var string|array the name of the MongoDB collection that stores the session data.
- * Please refer to [[Connection::getCollection()]] on how to specify this parameter.
- * This collection is better to be pre-created with fields 'id' and 'expire' indexed.
- */
- public $sessionCollection = 'session';
-
- /**
- * Initializes the Session component.
- * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection.
- * @throws InvalidConfigException if [[db]] is invalid.
- */
- public function init()
- {
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!$this->db instanceof Connection) {
- throw new InvalidConfigException($this->className() . "::db must be either a MongoDB connection instance or the application component ID of a MongoDB connection.");
- }
- parent::init();
- }
-
- /**
- * Returns a value indicating whether to use custom session storage.
- * This method overrides the parent implementation and always returns true.
- * @return boolean whether to use custom storage.
- */
- public function getUseCustomStorage()
- {
- return true;
- }
-
- /**
- * Updates the current session ID with a newly generated one.
- * Please refer to for more details.
- * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
- */
- public function regenerateID($deleteOldSession = false)
- {
- $oldID = session_id();
-
- // if no session is started, there is nothing to regenerate
- if (empty($oldID)) {
- return;
- }
-
- parent::regenerateID(false);
- $newID = session_id();
-
- $collection = $this->db->getCollection($this->sessionCollection);
- $row = $collection->findOne(['id' => $oldID]);
- if ($row !== null) {
- if ($deleteOldSession) {
- $collection->update(['id' => $oldID], ['id' => $newID]);
- } else {
- unset($row['_id']);
- $row['id'] = $newID;
- $collection->insert($row);
- }
- } else {
- // shouldn't reach here normally
- $collection->insert([
- 'id' => $newID,
- 'expire' => time() + $this->getTimeout()
- ]);
- }
- }
-
- /**
- * Session read handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return string the session data
- */
- public function readSession($id)
- {
- $collection = $this->db->getCollection($this->sessionCollection);
- $doc = $collection->findOne(
- [
- 'id' => $id,
- 'expire' => ['$gt' => time()],
- ],
- ['data' => 1, '_id' => 0]
- );
- return isset($doc['data']) ? $doc['data'] : '';
- }
-
- /**
- * Session write handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @param string $data session data
- * @return boolean whether session write is successful
- */
- public function writeSession($id, $data)
- {
- // exception must be caught in session write handler
- // http://us.php.net/manual/en/function.session-set-save-handler.php
- try {
- $this->db->getCollection($this->sessionCollection)->update(
- ['id' => $id],
- [
- 'id' => $id,
- 'data' => $data,
- 'expire' => time() + $this->getTimeout(),
- ],
- ['upsert' => true]
- );
- } catch (\Exception $e) {
- if (YII_DEBUG) {
- echo $e->getMessage();
- }
- // it is too late to log an error message here
- return false;
- }
- return true;
- }
-
- /**
- * Session destroy handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return boolean whether session is destroyed successfully
- */
- public function destroySession($id)
- {
- $this->db->getCollection($this->sessionCollection)->remove(
- ['id' => $id],
- ['justOne' => true]
- );
- return true;
- }
-
- /**
- * Session GC (garbage collection) handler.
- * Do not call this method directly.
- * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
- * @return boolean whether session is GCed successfully
- */
- public function gcSession($maxLifetime)
- {
- $this->db->getCollection($this->sessionCollection)
- ->remove(['expire' => ['$lt' => time()]]);
- return true;
- }
+ /**
+ * @var Connection|string the MongoDB connection object or the application component ID of the MongoDB connection.
+ * After the Session object is created, if you want to change this property, you should only assign it
+ * with a MongoDB connection object.
+ */
+ public $db = 'mongodb';
+ /**
+ * @var string|array the name of the MongoDB collection that stores the session data.
+ * Please refer to [[Connection::getCollection()]] on how to specify this parameter.
+ * This collection is better to be pre-created with fields 'id' and 'expire' indexed.
+ */
+ public $sessionCollection = 'session';
+
+ /**
+ * Initializes the Session component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException($this->className() . "::db must be either a MongoDB connection instance or the application component ID of a MongoDB connection.");
+ }
+ parent::init();
+ }
+
+ /**
+ * Returns a value indicating whether to use custom session storage.
+ * This method overrides the parent implementation and always returns true.
+ * @return boolean whether to use custom storage.
+ */
+ public function getUseCustomStorage()
+ {
+ return true;
+ }
+
+ /**
+ * Updates the current session ID with a newly generated one.
+ * Please refer to for more details.
+ * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
+ */
+ public function regenerateID($deleteOldSession = false)
+ {
+ $oldID = session_id();
+
+ // if no session is started, there is nothing to regenerate
+ if (empty($oldID)) {
+ return;
+ }
+
+ parent::regenerateID(false);
+ $newID = session_id();
+
+ $collection = $this->db->getCollection($this->sessionCollection);
+ $row = $collection->findOne(['id' => $oldID]);
+ if ($row !== null) {
+ if ($deleteOldSession) {
+ $collection->update(['id' => $oldID], ['id' => $newID]);
+ } else {
+ unset($row['_id']);
+ $row['id'] = $newID;
+ $collection->insert($row);
+ }
+ } else {
+ // shouldn't reach here normally
+ $collection->insert([
+ 'id' => $newID,
+ 'expire' => time() + $this->getTimeout()
+ ]);
+ }
+ }
+
+ /**
+ * Session read handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return string the session data
+ */
+ public function readSession($id)
+ {
+ $collection = $this->db->getCollection($this->sessionCollection);
+ $doc = $collection->findOne(
+ [
+ 'id' => $id,
+ 'expire' => ['$gt' => time()],
+ ],
+ ['data' => 1, '_id' => 0]
+ );
+
+ return isset($doc['data']) ? $doc['data'] : '';
+ }
+
+ /**
+ * Session write handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @param string $data session data
+ * @return boolean whether session write is successful
+ */
+ public function writeSession($id, $data)
+ {
+ // exception must be caught in session write handler
+ // http://us.php.net/manual/en/function.session-set-save-handler.php
+ try {
+ $this->db->getCollection($this->sessionCollection)->update(
+ ['id' => $id],
+ [
+ 'id' => $id,
+ 'data' => $data,
+ 'expire' => time() + $this->getTimeout(),
+ ],
+ ['upsert' => true]
+ );
+ } catch (\Exception $e) {
+ if (YII_DEBUG) {
+ echo $e->getMessage();
+ }
+ // it is too late to log an error message here
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Session destroy handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return boolean whether session is destroyed successfully
+ */
+ public function destroySession($id)
+ {
+ $this->db->getCollection($this->sessionCollection)->remove(
+ ['id' => $id],
+ ['justOne' => true]
+ );
+
+ return true;
+ }
+
+ /**
+ * Session GC (garbage collection) handler.
+ * Do not call this method directly.
+ * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
+ * @return boolean whether session is GCed successfully
+ */
+ public function gcSession($maxLifetime)
+ {
+ $this->db->getCollection($this->sessionCollection)
+ ->remove(['expire' => ['$lt' => time()]]);
+
+ return true;
+ }
}
diff --git a/extensions/mongodb/file/ActiveQuery.php b/extensions/mongodb/file/ActiveQuery.php
index d19043f8bb2..f8d4a203f46 100644
--- a/extensions/mongodb/file/ActiveQuery.php
+++ b/extensions/mongodb/file/ActiveQuery.php
@@ -37,84 +37,87 @@
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
- use ActiveQueryTrait;
- use ActiveRelationTrait;
+ use ActiveQueryTrait;
+ use ActiveRelationTrait;
- /**
- * Executes query and returns all results as an array.
- * @param \yii\mongodb\Connection $db the Mongo connection used to execute the query.
- * If null, the Mongo connection returned by [[modelClass]] will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- $cursor = $this->buildCursor($db);
- $rows = $this->fetchRows($cursor);
- if (!empty($rows)) {
- $models = $this->createModels($rows);
- if (!empty($this->with)) {
- $this->findWith($this->with, $models);
- }
- if (!$this->asArray) {
- foreach ($models as $model) {
- $model->afterFind();
- }
- }
- return $models;
- } else {
- return [];
- }
- }
+ /**
+ * Executes query and returns all results as an array.
+ * @param \yii\mongodb\Connection $db the Mongo connection used to execute the query.
+ * If null, the Mongo connection returned by [[modelClass]] will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $cursor = $this->buildCursor($db);
+ $rows = $this->fetchRows($cursor);
+ if (!empty($rows)) {
+ $models = $this->createModels($rows);
+ if (!empty($this->with)) {
+ $this->findWith($this->with, $models);
+ }
+ if (!$this->asArray) {
+ foreach ($models as $model) {
+ $model->afterFind();
+ }
+ }
- /**
- * Executes query and returns a single row of result.
- * @param \yii\mongodb\Connection $db the Mongo connection used to execute the query.
- * If null, the Mongo connection returned by [[modelClass]] will be used.
- * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
- * the query result may be either an array or an ActiveRecord object. Null will be returned
- * if the query results in nothing.
- */
- public function one($db = null)
- {
- $row = parent::one($db);
- if ($row !== false) {
- if ($this->asArray) {
- $model = $row;
- } else {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $model = $class::instantiate($row);
- $class::populateRecord($model, $row);
- }
- if (!empty($this->with)) {
- $models = [$model];
- $this->findWith($this->with, $models);
- $model = $models[0];
- }
- if (!$this->asArray) {
- $model->afterFind();
- }
- return $model;
- } else {
- return null;
- }
- }
+ return $models;
+ } else {
+ return [];
+ }
+ }
- /**
- * Returns the Mongo collection for this query.
- * @param \yii\mongodb\Connection $db Mongo connection.
- * @return Collection collection instance.
- */
- public function getCollection($db = null)
- {
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- if ($db === null) {
- $db = $modelClass::getDb();
- }
- if ($this->from === null) {
- $this->from = $modelClass::collectionName();
- }
- return $db->getFileCollection($this->from);
- }
+ /**
+ * Executes query and returns a single row of result.
+ * @param \yii\mongodb\Connection $db the Mongo connection used to execute the query.
+ * If null, the Mongo connection returned by [[modelClass]] will be used.
+ * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
+ * the query result may be either an array or an ActiveRecord object. Null will be returned
+ * if the query results in nothing.
+ */
+ public function one($db = null)
+ {
+ $row = parent::one($db);
+ if ($row !== false) {
+ if ($this->asArray) {
+ $model = $row;
+ } else {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $model = $class::instantiate($row);
+ $class::populateRecord($model, $row);
+ }
+ if (!empty($this->with)) {
+ $models = [$model];
+ $this->findWith($this->with, $models);
+ $model = $models[0];
+ }
+ if (!$this->asArray) {
+ $model->afterFind();
+ }
+
+ return $model;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the Mongo collection for this query.
+ * @param \yii\mongodb\Connection $db Mongo connection.
+ * @return Collection collection instance.
+ */
+ public function getCollection($db = null)
+ {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+ if ($db === null) {
+ $db = $modelClass::getDb();
+ }
+ if ($this->from === null) {
+ $this->from = $modelClass::collectionName();
+ }
+
+ return $db->getFileCollection($this->from);
+ }
}
diff --git a/extensions/mongodb/file/ActiveRecord.php b/extensions/mongodb/file/ActiveRecord.php
index 17372ec1f45..eec4b7bef93 100644
--- a/extensions/mongodb/file/ActiveRecord.php
+++ b/extensions/mongodb/file/ActiveRecord.php
@@ -44,306 +44,311 @@
*/
abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
{
- /**
- * Creates an [[ActiveQuery]] instance.
- *
- * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
- * by [[hasOne()]] and [[hasMany()]] to create a relational query.
- * You may override this method to return a customized query (e.g. `CustomerQuery` specified
- * written for querying `Customer` purpose.)
- *
- * You may also define default conditions that should apply to all queries unless overridden:
- *
- * ```php
- * public static function createQuery($config = [])
- * {
- * return parent::createQuery($config)->where(['deleted' => false]);
- * }
- * ```
- *
- * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
- * default condition. Using [[Query::where()]] will override the default condition.
- *
- * @param array $config the configuration passed to the ActiveQuery class.
- * @return ActiveQuery the newly created [[ActiveQuery]] instance.
- */
- public static function createQuery($config = [])
- {
- $config['modelClass'] = get_called_class();
- return new ActiveQuery($config);
- }
+ /**
+ * Creates an [[ActiveQuery]] instance.
+ *
+ * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
+ * by [[hasOne()]] and [[hasMany()]] to create a relational query.
+ * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+ * written for querying `Customer` purpose.)
+ *
+ * You may also define default conditions that should apply to all queries unless overridden:
+ *
+ * ```php
+ * public static function createQuery($config = [])
+ * {
+ * return parent::createQuery($config)->where(['deleted' => false]);
+ * }
+ * ```
+ *
+ * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
+ * default condition. Using [[Query::where()]] will override the default condition.
+ *
+ * @param array $config the configuration passed to the ActiveQuery class.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance.
+ */
+ public static function createQuery($config = [])
+ {
+ $config['modelClass'] = get_called_class();
- /**
- * Return the Mongo GridFS collection instance for this AR class.
- * @return Collection collection instance.
- */
- public static function getCollection()
- {
- return static::getDb()->getFileCollection(static::collectionName());
- }
+ return new ActiveQuery($config);
+ }
- /**
- * Returns the list of all attribute names of the model.
- * This method could be overridden by child classes to define available attributes.
- * Note: all attributes defined in base Active Record class should be always present
- * in returned array.
- * For example:
- * ~~~
- * public function attributes()
- * {
- * return array_merge(
- * parent::attributes(),
- * ['tags', 'status']
- * );
- * }
- * ~~~
- * @return array list of attribute names.
- */
- public function attributes()
- {
- return [
- '_id',
- 'filename',
- 'uploadDate',
- 'length',
- 'chunkSize',
- 'md5',
- 'file',
- 'newFileContent'
- ];
- }
+ /**
+ * Return the Mongo GridFS collection instance for this AR class.
+ * @return Collection collection instance.
+ */
+ public static function getCollection()
+ {
+ return static::getDb()->getFileCollection(static::collectionName());
+ }
- /**
- * @see ActiveRecord::insert()
- */
- protected function insertInternal($attributes = null)
- {
- if (!$this->beforeSave(true)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- $currentAttributes = $this->getAttributes();
- foreach ($this->primaryKey() as $key) {
- $values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null;
- }
- }
- $collection = static::getCollection();
- if (isset($values['newFileContent'])) {
- $newFileContent = $values['newFileContent'];
- unset($values['newFileContent']);
- }
- if (isset($values['file'])) {
- $newFile = $values['file'];
- unset($values['file']);
- }
- if (isset($newFileContent)) {
- $newId = $collection->insertFileContent($newFileContent, $values);
- } elseif (isset($newFile)) {
- $fileName = $this->extractFileName($newFile);
- $newId = $collection->insertFile($fileName, $values);
- } else {
- $newId = $collection->insert($values);
- }
- $this->setAttribute('_id', $newId);
- foreach ($values as $name => $value) {
- $this->setOldAttribute($name, $value);
- }
- $this->afterSave(true);
- return true;
- }
+ /**
+ * Returns the list of all attribute names of the model.
+ * This method could be overridden by child classes to define available attributes.
+ * Note: all attributes defined in base Active Record class should be always present
+ * in returned array.
+ * For example:
+ * ~~~
+ * public function attributes()
+ * {
+ * return array_merge(
+ * parent::attributes(),
+ * ['tags', 'status']
+ * );
+ * }
+ * ~~~
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ return [
+ '_id',
+ 'filename',
+ 'uploadDate',
+ 'length',
+ 'chunkSize',
+ 'md5',
+ 'file',
+ 'newFileContent'
+ ];
+ }
- /**
- * @see ActiveRecord::update()
- * @throws StaleObjectException
- */
- protected function updateInternal($attributes = null)
- {
- if (!$this->beforeSave(false)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- $this->afterSave(false);
- return 0;
- }
+ /**
+ * @see ActiveRecord::insert()
+ */
+ protected function insertInternal($attributes = null)
+ {
+ if (!$this->beforeSave(true)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ $currentAttributes = $this->getAttributes();
+ foreach ($this->primaryKey() as $key) {
+ $values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null;
+ }
+ }
+ $collection = static::getCollection();
+ if (isset($values['newFileContent'])) {
+ $newFileContent = $values['newFileContent'];
+ unset($values['newFileContent']);
+ }
+ if (isset($values['file'])) {
+ $newFile = $values['file'];
+ unset($values['file']);
+ }
+ if (isset($newFileContent)) {
+ $newId = $collection->insertFileContent($newFileContent, $values);
+ } elseif (isset($newFile)) {
+ $fileName = $this->extractFileName($newFile);
+ $newId = $collection->insertFile($fileName, $values);
+ } else {
+ $newId = $collection->insert($values);
+ }
+ $this->setAttribute('_id', $newId);
+ foreach ($values as $name => $value) {
+ $this->setOldAttribute($name, $value);
+ }
+ $this->afterSave(true);
- $collection = static::getCollection();
- if (isset($values['newFileContent'])) {
- $newFileContent = $values['newFileContent'];
- unset($values['newFileContent']);
- }
- if (isset($values['file'])) {
- $newFile = $values['file'];
- unset($values['file']);
- }
- if (isset($newFileContent) || isset($newFile)) {
- $fileAssociatedAttributeNames = [
- 'filename',
- 'uploadDate',
- 'length',
- 'chunkSize',
- 'md5',
- 'file',
- 'newFileContent'
- ];
- $values = array_merge($this->getAttributes(null, $fileAssociatedAttributeNames), $values);
- $rows = $this->deleteInternal();
- $insertValues = $values;
- $insertValues['_id'] = $this->getAttribute('_id');
- if (isset($newFileContent)) {
- $collection->insertFileContent($newFileContent, $insertValues);
- } else {
- $fileName = $this->extractFileName($newFile);
- $collection->insertFile($fileName, $insertValues);
- }
- $this->setAttribute('newFileContent', null);
- $this->setAttribute('file', null);
- } else {
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- if (!isset($values[$lock])) {
- $values[$lock] = $this->$lock + 1;
- }
- $condition[$lock] = $this->$lock;
- }
- // We do not check the return value of update() because it's possible
- // that it doesn't change anything and thus returns 0.
- $rows = $collection->update($condition, $values);
- if ($lock !== null && !$rows) {
- throw new StaleObjectException('The object being updated is outdated.');
- }
- }
+ return true;
+ }
- foreach ($values as $name => $value) {
- $this->setOldAttribute($name, $this->getAttribute($name));
- }
- $this->afterSave(false);
- return $rows;
- }
+ /**
+ * @see ActiveRecord::update()
+ * @throws StaleObjectException
+ */
+ protected function updateInternal($attributes = null)
+ {
+ if (!$this->beforeSave(false)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ $this->afterSave(false);
- /**
- * Extracts filename from given raw file value.
- * @param mixed $file raw file value.
- * @return string file name.
- * @throws \yii\base\InvalidParamException on invalid file value.
- */
- protected function extractFileName($file)
- {
- if ($file instanceof UploadedFile) {
- return $file->tempName;
- } elseif (is_string($file)) {
- if (file_exists($file)) {
- return $file;
- } else {
- throw new InvalidParamException("File '{$file}' does not exist.");
- }
- } else {
- throw new InvalidParamException('Unsupported type of "file" attribute.');
- }
- }
+ return 0;
+ }
- /**
- * Refreshes the [[file]] attribute from file collection, using current primary key.
- * @return \MongoGridFSFile|null refreshed file value.
- */
- public function refreshFile()
- {
- $mongoFile = $this->getCollection()->get($this->getPrimaryKey());
- $this->setAttribute('file', $mongoFile);
- return $mongoFile;
- }
+ $collection = static::getCollection();
+ if (isset($values['newFileContent'])) {
+ $newFileContent = $values['newFileContent'];
+ unset($values['newFileContent']);
+ }
+ if (isset($values['file'])) {
+ $newFile = $values['file'];
+ unset($values['file']);
+ }
+ if (isset($newFileContent) || isset($newFile)) {
+ $fileAssociatedAttributeNames = [
+ 'filename',
+ 'uploadDate',
+ 'length',
+ 'chunkSize',
+ 'md5',
+ 'file',
+ 'newFileContent'
+ ];
+ $values = array_merge($this->getAttributes(null, $fileAssociatedAttributeNames), $values);
+ $rows = $this->deleteInternal();
+ $insertValues = $values;
+ $insertValues['_id'] = $this->getAttribute('_id');
+ if (isset($newFileContent)) {
+ $collection->insertFileContent($newFileContent, $insertValues);
+ } else {
+ $fileName = $this->extractFileName($newFile);
+ $collection->insertFile($fileName, $insertValues);
+ }
+ $this->setAttribute('newFileContent', null);
+ $this->setAttribute('file', null);
+ } else {
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ if (!isset($values[$lock])) {
+ $values[$lock] = $this->$lock + 1;
+ }
+ $condition[$lock] = $this->$lock;
+ }
+ // We do not check the return value of update() because it's possible
+ // that it doesn't change anything and thus returns 0.
+ $rows = $collection->update($condition, $values);
+ if ($lock !== null && !$rows) {
+ throw new StaleObjectException('The object being updated is outdated.');
+ }
+ }
- /**
- * Returns the associated file content.
- * @return null|string file content.
- * @throws \yii\base\InvalidParamException on invalid file attribute value.
- */
- public function getFileContent()
- {
- $file = $this->getAttribute('file');
- if (empty($file) && !$this->getIsNewRecord()) {
- $file = $this->refreshFile();
- }
- if (empty($file)) {
- return null;
- } elseif ($file instanceof \MongoGridFSFile) {
- $fileSize = $file->getSize();
- if (empty($fileSize)) {
- return null;
- } else {
- return $file->getBytes();
- }
- } elseif ($file instanceof UploadedFile) {
- return file_get_contents($file->tempName);
- } elseif (is_string($file)) {
- if (file_exists($file)) {
- return file_get_contents($file);
- } else {
- throw new InvalidParamException("File '{$file}' does not exist.");
- }
- } else {
- throw new InvalidParamException('Unsupported type of "file" attribute.');
- }
- }
+ foreach ($values as $name => $value) {
+ $this->setOldAttribute($name, $this->getAttribute($name));
+ }
+ $this->afterSave(false);
- /**
- * Writes the the internal file content into the given filename.
- * @param string $filename full filename to be written.
- * @return boolean whether the operation was successful.
- * @throws \yii\base\InvalidParamException on invalid file attribute value.
- */
- public function writeFile($filename)
- {
- $file = $this->getAttribute('file');
- if (empty($file) && !$this->getIsNewRecord()) {
- $file = $this->refreshFile();
- }
- if (empty($file)) {
- throw new InvalidParamException('There is no file associated with this object.');
- } elseif ($file instanceof \MongoGridFSFile) {
- return ($file->write($filename) == $file->getSize());
- } elseif ($file instanceof UploadedFile) {
- return copy($file->tempName, $filename);
- } elseif (is_string($file)) {
- if (file_exists($file)) {
- return copy($file, $filename);
- } else {
- throw new InvalidParamException("File '{$file}' does not exist.");
- }
- } else {
- throw new InvalidParamException('Unsupported type of "file" attribute.');
- }
- }
+ return $rows;
+ }
- /**
- * This method returns a stream resource that can be used with all file functions in PHP,
- * which deal with reading files. The contents of the file are pulled out of MongoDB on the fly,
- * so that the whole file does not have to be loaded into memory first.
- * @return resource file stream resource.
- * @throws \yii\base\InvalidParamException on invalid file attribute value.
- */
- public function getFileResource()
- {
- $file = $this->getAttribute('file');
- if (empty($file) && !$this->getIsNewRecord()) {
- $file = $this->refreshFile();
- }
- if (empty($file)) {
- throw new InvalidParamException('There is no file associated with this object.');
- } elseif ($file instanceof \MongoGridFSFile) {
- return $file->getResource();
- } elseif ($file instanceof UploadedFile) {
- return fopen($file->tempName, 'r');
- } elseif (is_string($file)) {
- if (file_exists($file)) {
- return fopen($file, 'r');
- } else {
- throw new InvalidParamException("File '{$file}' does not exist.");
- }
- } else {
- throw new InvalidParamException('Unsupported type of "file" attribute.');
- }
- }
+ /**
+ * Extracts filename from given raw file value.
+ * @param mixed $file raw file value.
+ * @return string file name.
+ * @throws \yii\base\InvalidParamException on invalid file value.
+ */
+ protected function extractFileName($file)
+ {
+ if ($file instanceof UploadedFile) {
+ return $file->tempName;
+ } elseif (is_string($file)) {
+ if (file_exists($file)) {
+ return $file;
+ } else {
+ throw new InvalidParamException("File '{$file}' does not exist.");
+ }
+ } else {
+ throw new InvalidParamException('Unsupported type of "file" attribute.');
+ }
+ }
+
+ /**
+ * Refreshes the [[file]] attribute from file collection, using current primary key.
+ * @return \MongoGridFSFile|null refreshed file value.
+ */
+ public function refreshFile()
+ {
+ $mongoFile = $this->getCollection()->get($this->getPrimaryKey());
+ $this->setAttribute('file', $mongoFile);
+
+ return $mongoFile;
+ }
+
+ /**
+ * Returns the associated file content.
+ * @return null|string file content.
+ * @throws \yii\base\InvalidParamException on invalid file attribute value.
+ */
+ public function getFileContent()
+ {
+ $file = $this->getAttribute('file');
+ if (empty($file) && !$this->getIsNewRecord()) {
+ $file = $this->refreshFile();
+ }
+ if (empty($file)) {
+ return null;
+ } elseif ($file instanceof \MongoGridFSFile) {
+ $fileSize = $file->getSize();
+ if (empty($fileSize)) {
+ return null;
+ } else {
+ return $file->getBytes();
+ }
+ } elseif ($file instanceof UploadedFile) {
+ return file_get_contents($file->tempName);
+ } elseif (is_string($file)) {
+ if (file_exists($file)) {
+ return file_get_contents($file);
+ } else {
+ throw new InvalidParamException("File '{$file}' does not exist.");
+ }
+ } else {
+ throw new InvalidParamException('Unsupported type of "file" attribute.');
+ }
+ }
+
+ /**
+ * Writes the the internal file content into the given filename.
+ * @param string $filename full filename to be written.
+ * @return boolean whether the operation was successful.
+ * @throws \yii\base\InvalidParamException on invalid file attribute value.
+ */
+ public function writeFile($filename)
+ {
+ $file = $this->getAttribute('file');
+ if (empty($file) && !$this->getIsNewRecord()) {
+ $file = $this->refreshFile();
+ }
+ if (empty($file)) {
+ throw new InvalidParamException('There is no file associated with this object.');
+ } elseif ($file instanceof \MongoGridFSFile) {
+ return ($file->write($filename) == $file->getSize());
+ } elseif ($file instanceof UploadedFile) {
+ return copy($file->tempName, $filename);
+ } elseif (is_string($file)) {
+ if (file_exists($file)) {
+ return copy($file, $filename);
+ } else {
+ throw new InvalidParamException("File '{$file}' does not exist.");
+ }
+ } else {
+ throw new InvalidParamException('Unsupported type of "file" attribute.');
+ }
+ }
+
+ /**
+ * This method returns a stream resource that can be used with all file functions in PHP,
+ * which deal with reading files. The contents of the file are pulled out of MongoDB on the fly,
+ * so that the whole file does not have to be loaded into memory first.
+ * @return resource file stream resource.
+ * @throws \yii\base\InvalidParamException on invalid file attribute value.
+ */
+ public function getFileResource()
+ {
+ $file = $this->getAttribute('file');
+ if (empty($file) && !$this->getIsNewRecord()) {
+ $file = $this->refreshFile();
+ }
+ if (empty($file)) {
+ throw new InvalidParamException('There is no file associated with this object.');
+ } elseif ($file instanceof \MongoGridFSFile) {
+ return $file->getResource();
+ } elseif ($file instanceof UploadedFile) {
+ return fopen($file->tempName, 'r');
+ } elseif (is_string($file)) {
+ if (file_exists($file)) {
+ return fopen($file, 'r');
+ } else {
+ throw new InvalidParamException("File '{$file}' does not exist.");
+ }
+ } else {
+ throw new InvalidParamException('Unsupported type of "file" attribute.');
+ }
+ }
}
diff --git a/extensions/mongodb/file/Collection.php b/extensions/mongodb/file/Collection.php
index c4083648ebb..4ecc4c5d445 100644
--- a/extensions/mongodb/file/Collection.php
+++ b/extensions/mongodb/file/Collection.php
@@ -24,162 +24,169 @@
*/
class Collection extends \yii\mongodb\Collection
{
- /**
- * @var \MongoGridFS Mongo GridFS collection instance.
- */
- public $mongoCollection;
- /**
- * @var \yii\mongodb\Collection file chunks Mongo collection.
- */
- private $_chunkCollection;
-
- /**
- * Returns the Mongo collection for the file chunks.
- * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
- * @return \yii\mongodb\Collection mongo collection instance.
- */
- public function getChunkCollection($refresh = false)
- {
- if ($refresh || !is_object($this->_chunkCollection)) {
- $this->_chunkCollection = Yii::createObject([
- 'class' => 'yii\mongodb\Collection',
- 'mongoCollection' => $this->mongoCollection->chunks
- ]);
- }
- return $this->_chunkCollection;
- }
-
- /**
- * Removes data from the collection.
- * @param array $condition description of records to remove.
- * @param array $options list of options in format: optionName => optionValue.
- * @return integer|boolean number of updated documents or whether operation was successful.
- * @throws Exception on failure.
- */
- public function remove($condition = [], $options = [])
- {
- $result = parent::remove($condition, $options);
- $this->tryLastError(); // MongoGridFS::remove will return even if the remove failed
- return $result;
- }
-
- /**
- * Creates new file in GridFS collection from given local filesystem file.
- * Additional attributes can be added file document using $metadata.
- * @param string $filename name of the file to store.
- * @param array $metadata other metadata fields to include in the file document.
- * @param array $options list of options in format: optionName => optionValue
- * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
- * unless an "_id" was explicitly specified in the metadata.
- * @throws Exception on failure.
- */
- public function insertFile($filename, $metadata = [], $options = [])
- {
- $token = 'Inserting file into ' . $this->getFullName();
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $options = array_merge(['w' => 1], $options);
- $result = $this->mongoCollection->storeFile($filename, $metadata, $options);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Creates new file in GridFS collection with specified content.
- * Additional attributes can be added file document using $metadata.
- * @param string $bytes string of bytes to store.
- * @param array $metadata other metadata fields to include in the file document.
- * @param array $options list of options in format: optionName => optionValue
- * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
- * unless an "_id" was explicitly specified in the metadata.
- * @throws Exception on failure.
- */
- public function insertFileContent($bytes, $metadata = [], $options = [])
- {
- $token = 'Inserting file content into ' . $this->getFullName();
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $options = array_merge(['w' => 1], $options);
- $result = $this->mongoCollection->storeBytes($bytes, $metadata, $options);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Creates new file in GridFS collection from uploaded file.
- * Additional attributes can be added file document using $metadata.
- * @param string $name name of the uploaded file to store. This should correspond to
- * the file field's name attribute in the HTML form.
- * @param array $metadata other metadata fields to include in the file document.
- * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
- * unless an "_id" was explicitly specified in the metadata.
- * @throws Exception on failure.
- */
- public function insertUploads($name, $metadata = [])
- {
- $token = 'Inserting file uploads into ' . $this->getFullName();
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->storeUpload($name, $metadata);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Retrieves the file with given _id.
- * @param mixed $id _id of the file to find.
- * @return \MongoGridFSFile|null found file, or null if file does not exist
- * @throws Exception on failure.
- */
- public function get($id)
- {
- $token = 'Inserting file uploads into ' . $this->getFullName();
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->get($id);
- Yii::endProfile($token, __METHOD__);
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- /**
- * Deletes the file with given _id.
- * @param mixed $id _id of the file to find.
- * @return boolean whether the operation was successful.
- * @throws Exception on failure.
- */
- public function delete($id)
- {
- $token = 'Inserting file uploads into ' . $this->getFullName();
- Yii::info($token, __METHOD__);
- try {
- Yii::beginProfile($token, __METHOD__);
- $result = $this->mongoCollection->delete($id);
- $this->tryResultError($result);
- Yii::endProfile($token, __METHOD__);
- return true;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
+ /**
+ * @var \MongoGridFS Mongo GridFS collection instance.
+ */
+ public $mongoCollection;
+ /**
+ * @var \yii\mongodb\Collection file chunks Mongo collection.
+ */
+ private $_chunkCollection;
+
+ /**
+ * Returns the Mongo collection for the file chunks.
+ * @param boolean $refresh whether to reload the collection instance even if it is found in the cache.
+ * @return \yii\mongodb\Collection mongo collection instance.
+ */
+ public function getChunkCollection($refresh = false)
+ {
+ if ($refresh || !is_object($this->_chunkCollection)) {
+ $this->_chunkCollection = Yii::createObject([
+ 'class' => 'yii\mongodb\Collection',
+ 'mongoCollection' => $this->mongoCollection->chunks
+ ]);
+ }
+
+ return $this->_chunkCollection;
+ }
+
+ /**
+ * Removes data from the collection.
+ * @param array $condition description of records to remove.
+ * @param array $options list of options in format: optionName => optionValue.
+ * @return integer|boolean number of updated documents or whether operation was successful.
+ * @throws Exception on failure.
+ */
+ public function remove($condition = [], $options = [])
+ {
+ $result = parent::remove($condition, $options);
+ $this->tryLastError(); // MongoGridFS::remove will return even if the remove failed
+
+ return $result;
+ }
+
+ /**
+ * Creates new file in GridFS collection from given local filesystem file.
+ * Additional attributes can be added file document using $metadata.
+ * @param string $filename name of the file to store.
+ * @param array $metadata other metadata fields to include in the file document.
+ * @param array $options list of options in format: optionName => optionValue
+ * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
+ * unless an "_id" was explicitly specified in the metadata.
+ * @throws Exception on failure.
+ */
+ public function insertFile($filename, $metadata = [], $options = [])
+ {
+ $token = 'Inserting file into ' . $this->getFullName();
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $options = array_merge(['w' => 1], $options);
+ $result = $this->mongoCollection->storeFile($filename, $metadata, $options);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Creates new file in GridFS collection with specified content.
+ * Additional attributes can be added file document using $metadata.
+ * @param string $bytes string of bytes to store.
+ * @param array $metadata other metadata fields to include in the file document.
+ * @param array $options list of options in format: optionName => optionValue
+ * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
+ * unless an "_id" was explicitly specified in the metadata.
+ * @throws Exception on failure.
+ */
+ public function insertFileContent($bytes, $metadata = [], $options = [])
+ {
+ $token = 'Inserting file content into ' . $this->getFullName();
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $options = array_merge(['w' => 1], $options);
+ $result = $this->mongoCollection->storeBytes($bytes, $metadata, $options);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Creates new file in GridFS collection from uploaded file.
+ * Additional attributes can be added file document using $metadata.
+ * @param string $name name of the uploaded file to store. This should correspond to
+ * the file field's name attribute in the HTML form.
+ * @param array $metadata other metadata fields to include in the file document.
+ * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
+ * unless an "_id" was explicitly specified in the metadata.
+ * @throws Exception on failure.
+ */
+ public function insertUploads($name, $metadata = [])
+ {
+ $token = 'Inserting file uploads into ' . $this->getFullName();
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->storeUpload($name, $metadata);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieves the file with given _id.
+ * @param mixed $id _id of the file to find.
+ * @return \MongoGridFSFile|null found file, or null if file does not exist
+ * @throws Exception on failure.
+ */
+ public function get($id)
+ {
+ $token = 'Inserting file uploads into ' . $this->getFullName();
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->get($id);
+ Yii::endProfile($token, __METHOD__);
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Deletes the file with given _id.
+ * @param mixed $id _id of the file to find.
+ * @return boolean whether the operation was successful.
+ * @throws Exception on failure.
+ */
+ public function delete($id)
+ {
+ $token = 'Inserting file uploads into ' . $this->getFullName();
+ Yii::info($token, __METHOD__);
+ try {
+ Yii::beginProfile($token, __METHOD__);
+ $result = $this->mongoCollection->delete($id);
+ $this->tryResultError($result);
+ Yii::endProfile($token, __METHOD__);
+
+ return true;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
}
diff --git a/extensions/mongodb/file/Query.php b/extensions/mongodb/file/Query.php
index 341113e2b2c..c801828fa11 100644
--- a/extensions/mongodb/file/Query.php
+++ b/extensions/mongodb/file/Query.php
@@ -23,53 +23,55 @@
*/
class Query extends \yii\mongodb\Query
{
- /**
- * Returns the Mongo collection for this query.
- * @param \yii\mongodb\Connection $db Mongo connection.
- * @return Collection collection instance.
- */
- public function getCollection($db = null)
- {
- if ($db === null) {
- $db = Yii::$app->getComponent('mongodb');
- }
- return $db->getFileCollection($this->from);
- }
+ /**
+ * Returns the Mongo collection for this query.
+ * @param \yii\mongodb\Connection $db Mongo connection.
+ * @return Collection collection instance.
+ */
+ public function getCollection($db = null)
+ {
+ if ($db === null) {
+ $db = Yii::$app->getComponent('mongodb');
+ }
- /**
- * @param \MongoGridFSCursor $cursor Mongo cursor instance to fetch data from.
- * @param boolean $all whether to fetch all rows or only first one.
- * @param string|callable $indexBy value to index by.
- * @return array|boolean result.
- * @see Query::fetchRows()
- */
- protected function fetchRowsInternal($cursor, $all, $indexBy)
- {
- $result = [];
- if ($all) {
- foreach ($cursor as $file) {
- $row = $file->file;
- $row['file'] = $file;
- if ($indexBy !== null) {
- if (is_string($indexBy)) {
- $key = $row[$indexBy];
- } else {
- $key = call_user_func($indexBy, $row);
- }
- $result[$key] = $row;
- } else {
- $result[] = $row;
- }
- }
- } else {
- if ($cursor->hasNext()) {
- $file = $cursor->getNext();
- $result = $file->file;
- $result['file'] = $file;
- } else {
- $result = false;
- }
- }
- return $result;
- }
+ return $db->getFileCollection($this->from);
+ }
+
+ /**
+ * @param \MongoGridFSCursor $cursor Mongo cursor instance to fetch data from.
+ * @param boolean $all whether to fetch all rows or only first one.
+ * @param string|callable $indexBy value to index by.
+ * @return array|boolean result.
+ * @see Query::fetchRows()
+ */
+ protected function fetchRowsInternal($cursor, $all, $indexBy)
+ {
+ $result = [];
+ if ($all) {
+ foreach ($cursor as $file) {
+ $row = $file->file;
+ $row['file'] = $file;
+ if ($indexBy !== null) {
+ if (is_string($indexBy)) {
+ $key = $row[$indexBy];
+ } else {
+ $key = call_user_func($indexBy, $row);
+ }
+ $result[$key] = $row;
+ } else {
+ $result[] = $row;
+ }
+ }
+ } else {
+ if ($cursor->hasNext()) {
+ $file = $cursor->getNext();
+ $result = $file->file;
+ $result['file'] = $file;
+ } else {
+ $result = false;
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/extensions/redis/ActiveQuery.php b/extensions/redis/ActiveQuery.php
index 54aec1f1bdf..7fc0a3267ab 100644
--- a/extensions/redis/ActiveQuery.php
+++ b/extensions/redis/ActiveQuery.php
@@ -72,374 +72,382 @@
*/
class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
{
- use QueryTrait;
- use ActiveQueryTrait;
- use ActiveRelationTrait;
-
- /**
- * Executes the query and returns all results as an array.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- // TODO add support for orderBy
- $data = $this->executeScript($db, 'All');
- $rows = [];
- foreach ($data as $dataRow) {
- $row = [];
- $c = count($dataRow);
- for ($i = 0; $i < $c;) {
- $row[$dataRow[$i++]] = $dataRow[$i++];
- }
- $rows[] = $row;
- }
- if (!empty($rows)) {
- $models = $this->createModels($rows);
- if (!empty($this->with)) {
- $this->findWith($this->with, $models);
- }
- if (!$this->asArray) {
- foreach ($models as $model) {
- $model->afterFind();
- }
- }
- return $models;
- } else {
- return [];
- }
- }
-
- /**
- * Executes the query and returns a single row of result.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
- * the query result may be either an array or an ActiveRecord object. Null will be returned
- * if the query results in nothing.
- */
- public function one($db = null)
- {
- // TODO add support for orderBy
- $data = $this->executeScript($db, 'One');
- if (empty($data)) {
- return null;
- }
- $row = [];
- $c = count($data);
- for ($i = 0; $i < $c;) {
- $row[$data[$i++]] = $data[$i++];
- }
- if ($this->asArray) {
- $model = $row;
- } else {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $model = $class::instantiate($row);
- $class::populateRecord($model, $row);
- }
- if (!empty($this->with)) {
- $models = [$model];
- $this->findWith($this->with, $models);
- $model = $models[0];
- }
- if (!$this->asArray) {
- $model->afterFind();
- }
- return $model;
- }
-
- /**
- * Returns the number of records.
- * @param string $q the COUNT expression. This parameter is ignored by this implementation.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer number of records
- */
- public function count($q = '*', $db = null)
- {
- if ($this->where === null) {
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- if ($db === null) {
- $db = $modelClass::getDb();
- }
- return $db->executeCommand('LLEN', [$modelClass::keyPrefix()]);
- } else {
- return $this->executeScript($db, 'Count');
- }
- }
-
- /**
- * Returns a value indicating whether the query result contains any row of data.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return boolean whether the query result contains any row of data.
- */
- public function exists($db = null)
- {
- return $this->one($db) !== null;
- }
-
- /**
- * Executes the query and returns the first column of the result.
- * @param string $column name of the column to select
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return array the first column of the query result. An empty array is returned if the query results in nothing.
- */
- public function column($column, $db = null)
- {
- // TODO add support for orderBy
- return $this->executeScript($db, 'Column', $column);
- }
-
- /**
- * Returns the number of records.
- * @param string $column the column to sum up
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer number of records
- */
- public function sum($column, $db = null)
- {
- return $this->executeScript($db, 'Sum', $column);
- }
-
- /**
- * Returns the average of the specified column values.
- * @param string $column the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the average of the specified column values.
- */
- public function average($column, $db = null)
- {
- return $this->executeScript($db, 'Average', $column);
- }
-
- /**
- * Returns the minimum of the specified column values.
- * @param string $column the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the minimum of the specified column values.
- */
- public function min($column, $db = null)
- {
- return $this->executeScript($db, 'Min', $column);
- }
-
- /**
- * Returns the maximum of the specified column values.
- * @param string $column the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the maximum of the specified column values.
- */
- public function max($column, $db = null)
- {
- return $this->executeScript($db, 'Max', $column);
- }
-
- /**
- * Returns the query result as a scalar value.
- * The value returned will be the specified attribute in the first record of the query results.
- * @param string $attribute name of the attribute to select
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return string the value of the specified attribute in the first record of the query result.
- * Null is returned if the query result is empty.
- */
- public function scalar($attribute, $db = null)
- {
- $record = $this->one($db);
- if ($record !== null) {
- return $record->hasAttribute($attribute) ? $record->$attribute : null;
- } else {
- return null;
- }
- }
-
-
- /**
- * Executes a script created by [[LuaScriptBuilder]]
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @param string $type the type of the script to generate
- * @param string $columnName
- * @return array|bool|null|string
- */
- protected function executeScript($db, $type, $columnName = null)
- {
- if ($this->primaryModel !== null) {
- // lazy loading
- if ($this->via instanceof self) {
- // via pivot table
- $viaModels = $this->via->findPivotRows([$this->primaryModel]);
- $this->filterByModels($viaModels);
- } elseif (is_array($this->via)) {
- // via relation
- /** @var ActiveQuery $viaQuery */
- list($viaName, $viaQuery) = $this->via;
- if ($viaQuery->multiple) {
- $viaModels = $viaQuery->all();
- $this->primaryModel->populateRelation($viaName, $viaModels);
- } else {
- $model = $viaQuery->one();
- $this->primaryModel->populateRelation($viaName, $model);
- $viaModels = $model === null ? [] : [$model];
- }
- $this->filterByModels($viaModels);
- } else {
- $this->filterByModels([$this->primaryModel]);
- }
- }
-
- if (!empty($this->orderBy)) {
- throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.');
- }
-
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
-
- if ($db === null) {
- $db = $modelClass::getDb();
- }
-
- // find by primary key if possible. This is much faster than scanning all records
- if (is_array($this->where) && !isset($this->where[0]) && $modelClass::isPrimaryKey(array_keys($this->where))) {
- return $this->findByPk($db, $type, $columnName);
- }
-
- $method = 'build' . $type;
- $script = $db->getLuaScriptBuilder()->$method($this, $columnName);
- return $db->executeCommand('EVAL', [$script, 0]);
- }
-
- /**
- * Fetch by pk if possible as this is much faster
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @param string $type the type of the script to generate
- * @param string $columnName
- * @return array|bool|null|string
- * @throws \yii\base\InvalidParamException
- * @throws \yii\base\NotSupportedException
- */
- private function findByPk($db, $type, $columnName = null)
- {
- if (count($this->where) == 1) {
- $pks = (array) reset($this->where);
- } else {
- 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.');
- }
- }
- $pks = [$this->where];
- }
-
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
-
- if ($type == 'Count') {
- $start = 0;
- $limit = null;
- } else {
- $start = $this->offset === null ? 0 : $this->offset;
- $limit = $this->limit;
- }
- $i = 0;
- $data = [];
- foreach ($pks as $pk) {
- if (++$i > $start && ($limit === null || $i <= $start + $limit)) {
- $key = $modelClass::keyPrefix() . ':a:' . $modelClass::buildKey($pk);
- $result = $db->executeCommand('HGETALL', [$key]);
- if (!empty($result)) {
- $data[] = $result;
- if ($type === 'One' && $this->orderBy === null) {
- break;
- }
- }
- }
- }
- // TODO support orderBy
-
- switch($type) {
- case 'All':
- return $data;
- case 'One':
- return reset($data);
- case 'Count':
- return count($data);
- case 'Column':
- $column = [];
- foreach ($data as $dataRow) {
- $row = [];
- $c = count($dataRow);
- for ($i = 0; $i < $c;) {
- $row[$dataRow[$i++]] = $dataRow[$i++];
- }
- $column[] = $row[$columnName];
- }
- return $column;
- case 'Sum':
- $sum = 0;
- foreach ($data as $dataRow) {
- $c = count($dataRow);
- for ($i = 0; $i < $c;) {
- if ($dataRow[$i++] == $columnName) {
- $sum += $dataRow[$i];
- break;
- }
- }
- }
- return $sum;
- case 'Average':
- $sum = 0;
- $count = 0;
- foreach ($data as $dataRow) {
- $count++;
- $c = count($dataRow);
- for ($i = 0; $i < $c;) {
- if ($dataRow[$i++] == $columnName) {
- $sum += $dataRow[$i];
- break;
- }
- }
- }
- return $sum / $count;
- case 'Min':
- $min = null;
- foreach ($data as $dataRow) {
- $c = count($dataRow);
- for ($i = 0; $i < $c;) {
- if ($dataRow[$i++] == $columnName && ($min == null || $dataRow[$i] < $min)) {
- $min = $dataRow[$i];
- break;
- }
- }
- }
- return $min;
- case 'Max':
- $max = null;
- foreach ($data as $dataRow) {
- $c = count($dataRow);
- for ($i = 0; $i < $c;) {
- if ($dataRow[$i++] == $columnName && ($max == null || $dataRow[$i] > $max)) {
- $max = $dataRow[$i];
- break;
- }
- }
- }
- return $max;
- }
- throw new InvalidParamException('Unknown fetch type: ' . $type);
- }
+ use QueryTrait;
+ use ActiveQueryTrait;
+ use ActiveRelationTrait;
+
+ /**
+ * Executes the query and returns all results as an array.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ // TODO add support for orderBy
+ $data = $this->executeScript($db, 'All');
+ $rows = [];
+ foreach ($data as $dataRow) {
+ $row = [];
+ $c = count($dataRow);
+ for ($i = 0; $i < $c;) {
+ $row[$dataRow[$i++]] = $dataRow[$i++];
+ }
+ $rows[] = $row;
+ }
+ if (!empty($rows)) {
+ $models = $this->createModels($rows);
+ if (!empty($this->with)) {
+ $this->findWith($this->with, $models);
+ }
+ if (!$this->asArray) {
+ foreach ($models as $model) {
+ $model->afterFind();
+ }
+ }
+
+ return $models;
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * Executes the query and returns a single row of result.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
+ * the query result may be either an array or an ActiveRecord object. Null will be returned
+ * if the query results in nothing.
+ */
+ public function one($db = null)
+ {
+ // TODO add support for orderBy
+ $data = $this->executeScript($db, 'One');
+ if (empty($data)) {
+ return null;
+ }
+ $row = [];
+ $c = count($data);
+ for ($i = 0; $i < $c;) {
+ $row[$data[$i++]] = $data[$i++];
+ }
+ if ($this->asArray) {
+ $model = $row;
+ } else {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $model = $class::instantiate($row);
+ $class::populateRecord($model, $row);
+ }
+ if (!empty($this->with)) {
+ $models = [$model];
+ $this->findWith($this->with, $models);
+ $model = $models[0];
+ }
+ if (!$this->asArray) {
+ $model->afterFind();
+ }
+
+ return $model;
+ }
+
+ /**
+ * Returns the number of records.
+ * @param string $q the COUNT expression. This parameter is ignored by this implementation.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer number of records
+ */
+ public function count($q = '*', $db = null)
+ {
+ if ($this->where === null) {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+ if ($db === null) {
+ $db = $modelClass::getDb();
+ }
+
+ return $db->executeCommand('LLEN', [$modelClass::keyPrefix()]);
+ } else {
+ return $this->executeScript($db, 'Count');
+ }
+ }
+
+ /**
+ * Returns a value indicating whether the query result contains any row of data.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return boolean whether the query result contains any row of data.
+ */
+ public function exists($db = null)
+ {
+ return $this->one($db) !== null;
+ }
+
+ /**
+ * Executes the query and returns the first column of the result.
+ * @param string $column name of the column to select
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array the first column of the query result. An empty array is returned if the query results in nothing.
+ */
+ public function column($column, $db = null)
+ {
+ // TODO add support for orderBy
+ return $this->executeScript($db, 'Column', $column);
+ }
+
+ /**
+ * Returns the number of records.
+ * @param string $column the column to sum up
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer number of records
+ */
+ public function sum($column, $db = null)
+ {
+ return $this->executeScript($db, 'Sum', $column);
+ }
+
+ /**
+ * Returns the average of the specified column values.
+ * @param string $column the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the average of the specified column values.
+ */
+ public function average($column, $db = null)
+ {
+ return $this->executeScript($db, 'Average', $column);
+ }
+
+ /**
+ * Returns the minimum of the specified column values.
+ * @param string $column the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the minimum of the specified column values.
+ */
+ public function min($column, $db = null)
+ {
+ return $this->executeScript($db, 'Min', $column);
+ }
+
+ /**
+ * Returns the maximum of the specified column values.
+ * @param string $column the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the maximum of the specified column values.
+ */
+ public function max($column, $db = null)
+ {
+ return $this->executeScript($db, 'Max', $column);
+ }
+
+ /**
+ * Returns the query result as a scalar value.
+ * The value returned will be the specified attribute in the first record of the query results.
+ * @param string $attribute name of the attribute to select
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return string the value of the specified attribute in the first record of the query result.
+ * Null is returned if the query result is empty.
+ */
+ public function scalar($attribute, $db = null)
+ {
+ $record = $this->one($db);
+ if ($record !== null) {
+ return $record->hasAttribute($attribute) ? $record->$attribute : null;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Executes a script created by [[LuaScriptBuilder]]
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @param string $type the type of the script to generate
+ * @param string $columnName
+ * @return array|bool|null|string
+ */
+ protected function executeScript($db, $type, $columnName = null)
+ {
+ if ($this->primaryModel !== null) {
+ // lazy loading
+ if ($this->via instanceof self) {
+ // via pivot table
+ $viaModels = $this->via->findPivotRows([$this->primaryModel]);
+ $this->filterByModels($viaModels);
+ } elseif (is_array($this->via)) {
+ // via relation
+ /** @var ActiveQuery $viaQuery */
+ list($viaName, $viaQuery) = $this->via;
+ if ($viaQuery->multiple) {
+ $viaModels = $viaQuery->all();
+ $this->primaryModel->populateRelation($viaName, $viaModels);
+ } else {
+ $model = $viaQuery->one();
+ $this->primaryModel->populateRelation($viaName, $model);
+ $viaModels = $model === null ? [] : [$model];
+ }
+ $this->filterByModels($viaModels);
+ } else {
+ $this->filterByModels([$this->primaryModel]);
+ }
+ }
+
+ if (!empty($this->orderBy)) {
+ throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.');
+ }
+
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+
+ if ($db === null) {
+ $db = $modelClass::getDb();
+ }
+
+ // find by primary key if possible. This is much faster than scanning all records
+ if (is_array($this->where) && !isset($this->where[0]) && $modelClass::isPrimaryKey(array_keys($this->where))) {
+ return $this->findByPk($db, $type, $columnName);
+ }
+
+ $method = 'build' . $type;
+ $script = $db->getLuaScriptBuilder()->$method($this, $columnName);
+
+ return $db->executeCommand('EVAL', [$script, 0]);
+ }
+
+ /**
+ * Fetch by pk if possible as this is much faster
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @param string $type the type of the script to generate
+ * @param string $columnName
+ * @return array|bool|null|string
+ * @throws \yii\base\InvalidParamException
+ * @throws \yii\base\NotSupportedException
+ */
+ private function findByPk($db, $type, $columnName = null)
+ {
+ if (count($this->where) == 1) {
+ $pks = (array) reset($this->where);
+ } else {
+ 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.');
+ }
+ }
+ $pks = [$this->where];
+ }
+
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+
+ if ($type == 'Count') {
+ $start = 0;
+ $limit = null;
+ } else {
+ $start = $this->offset === null ? 0 : $this->offset;
+ $limit = $this->limit;
+ }
+ $i = 0;
+ $data = [];
+ foreach ($pks as $pk) {
+ if (++$i > $start && ($limit === null || $i <= $start + $limit)) {
+ $key = $modelClass::keyPrefix() . ':a:' . $modelClass::buildKey($pk);
+ $result = $db->executeCommand('HGETALL', [$key]);
+ if (!empty($result)) {
+ $data[] = $result;
+ if ($type === 'One' && $this->orderBy === null) {
+ break;
+ }
+ }
+ }
+ }
+ // TODO support orderBy
+
+ switch ($type) {
+ case 'All':
+ return $data;
+ case 'One':
+ return reset($data);
+ case 'Count':
+ return count($data);
+ case 'Column':
+ $column = [];
+ foreach ($data as $dataRow) {
+ $row = [];
+ $c = count($dataRow);
+ for ($i = 0; $i < $c;) {
+ $row[$dataRow[$i++]] = $dataRow[$i++];
+ }
+ $column[] = $row[$columnName];
+ }
+
+ return $column;
+ case 'Sum':
+ $sum = 0;
+ foreach ($data as $dataRow) {
+ $c = count($dataRow);
+ for ($i = 0; $i < $c;) {
+ if ($dataRow[$i++] == $columnName) {
+ $sum += $dataRow[$i];
+ break;
+ }
+ }
+ }
+
+ return $sum;
+ case 'Average':
+ $sum = 0;
+ $count = 0;
+ foreach ($data as $dataRow) {
+ $count++;
+ $c = count($dataRow);
+ for ($i = 0; $i < $c;) {
+ if ($dataRow[$i++] == $columnName) {
+ $sum += $dataRow[$i];
+ break;
+ }
+ }
+ }
+
+ return $sum / $count;
+ case 'Min':
+ $min = null;
+ foreach ($data as $dataRow) {
+ $c = count($dataRow);
+ for ($i = 0; $i < $c;) {
+ if ($dataRow[$i++] == $columnName && ($min == null || $dataRow[$i] < $min)) {
+ $min = $dataRow[$i];
+ break;
+ }
+ }
+ }
+
+ return $min;
+ case 'Max':
+ $max = null;
+ foreach ($data as $dataRow) {
+ $c = count($dataRow);
+ for ($i = 0; $i < $c;) {
+ if ($dataRow[$i++] == $columnName && ($max == null || $dataRow[$i] > $max)) {
+ $max = $dataRow[$i];
+ break;
+ }
+ }
+ }
+
+ return $max;
+ }
+ throw new InvalidParamException('Unknown fetch type: ' . $type);
+ }
}
diff --git a/extensions/redis/ActiveRecord.php b/extensions/redis/ActiveRecord.php
index 4a9050fe28a..980265a95ff 100644
--- a/extensions/redis/ActiveRecord.php
+++ b/extensions/redis/ActiveRecord.php
@@ -37,282 +37,291 @@
*/
class ActiveRecord extends BaseActiveRecord
{
- /**
- * Returns the database connection used by this AR class.
- * By default, the "redis" application component is used as the database connection.
- * You may override this method if you want to use a different database connection.
- * @return Connection the database connection used by this AR class.
- */
- public static function getDb()
- {
- return \Yii::$app->getComponent('redis');
- }
+ /**
+ * Returns the database connection used by this AR class.
+ * By default, the "redis" application component is used as the database connection.
+ * You may override this method if you want to use a different database connection.
+ * @return Connection the database connection used by this AR class.
+ */
+ public static function getDb()
+ {
+ return \Yii::$app->getComponent('redis');
+ }
- /**
- * Creates an [[ActiveQuery]] instance.
- *
- * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
- * by [[hasOne()]] and [[hasMany()]] to create a relational query.
- * You may override this method to return a customized query (e.g. `CustomerQuery` specified
- * written for querying `Customer` purpose.)
- *
- * You may also define default conditions that should apply to all queries unless overridden:
- *
- * ```php
- * public static function createQuery($config = [])
- * {
- * return parent::createQuery($config)->where(['deleted' => false]);
- * }
- * ```
- *
- * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
- * default condition. Using [[Query::where()]] will override the default condition.
- *
- * @param array $config the configuration passed to the ActiveQuery class.
- * @return ActiveQuery the newly created [[ActiveQuery]] instance.
- */
- public static function createQuery($config = [])
- {
- $config['modelClass'] = get_called_class();
- return new ActiveQuery($config);
- }
+ /**
+ * Creates an [[ActiveQuery]] instance.
+ *
+ * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
+ * by [[hasOne()]] and [[hasMany()]] to create a relational query.
+ * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+ * written for querying `Customer` purpose.)
+ *
+ * You may also define default conditions that should apply to all queries unless overridden:
+ *
+ * ```php
+ * public static function createQuery($config = [])
+ * {
+ * return parent::createQuery($config)->where(['deleted' => false]);
+ * }
+ * ```
+ *
+ * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
+ * default condition. Using [[Query::where()]] will override the default condition.
+ *
+ * @param array $config the configuration passed to the ActiveQuery class.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance.
+ */
+ public static function createQuery($config = [])
+ {
+ $config['modelClass'] = get_called_class();
- /**
- * Returns the primary key name(s) for this AR class.
- * This method should be overridden by child classes to define the primary key.
- *
- * Note that an array should be returned even when it is a single primary key.
- *
- * @return string[] the primary keys of this record.
- */
- public static function primaryKey()
- {
- return ['id'];
- }
+ return new ActiveQuery($config);
+ }
- /**
- * Returns the list of all attribute names of the model.
- * This method must be overridden by child classes to define available attributes.
- * @return array list of attribute names.
- */
- public function attributes()
- {
- throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.');
- }
+ /**
+ * Returns the primary key name(s) for this AR class.
+ * This method should be overridden by child classes to define the primary key.
+ *
+ * Note that an array should be returned even when it is a single primary key.
+ *
+ * @return string[] the primary keys of this record.
+ */
+ public static function primaryKey()
+ {
+ return ['id'];
+ }
- /**
- * Declares prefix of the key that represents the keys that store this records in redis.
- * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]].
- * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
- * 'order_item'. You may override this method if you want different key naming.
- * @return string the prefix to apply to all AR keys
- */
- public static function keyPrefix()
- {
- return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
- }
+ /**
+ * Returns the list of all attribute names of the model.
+ * This method must be overridden by child classes to define available attributes.
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.');
+ }
- /**
- * @inheritdoc
- */
- public function insert($runValidation = true, $attributes = null)
- {
- if ($runValidation && !$this->validate($attributes)) {
- return false;
- }
- if ($this->beforeSave(true)) {
- $db = static::getDb();
- $values = $this->getDirtyAttributes($attributes);
- $pk = [];
+ /**
+ * Declares prefix of the key that represents the keys that store this records in redis.
+ * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]].
+ * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
+ * 'order_item'. You may override this method if you want different key naming.
+ * @return string the prefix to apply to all AR keys
+ */
+ public static function keyPrefix()
+ {
+ return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function insert($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ if ($this->beforeSave(true)) {
+ $db = static::getDb();
+ $values = $this->getDirtyAttributes($attributes);
+ $pk = [];
// if ($values === []) {
- foreach ($this->primaryKey() as $key) {
- $pk[$key] = $values[$key] = $this->getAttribute($key);
- if ($pk[$key] === null) {
- $pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]);
- $this->setAttribute($key, $values[$key]);
- }
- }
+ foreach ($this->primaryKey() as $key) {
+ $pk[$key] = $values[$key] = $this->getAttribute($key);
+ if ($pk[$key] === null) {
+ $pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]);
+ $this->setAttribute($key, $values[$key]);
+ }
+ }
// }
- // save pk in a findall pool
- $db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]);
+ // save pk in a findall pool
+ $db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]);
+
+ $key = static::keyPrefix() . ':a:' . static::buildKey($pk);
+ // save attributes
+ $args = [$key];
+ foreach ($values as $attribute => $value) {
+ $args[] = $attribute;
+ $args[] = $value;
+ }
+ $db->executeCommand('HMSET', $args);
+
+ $this->setOldAttributes($values);
+ $this->afterSave(true);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Updates the whole table using the provided attribute values and conditions.
+ * For example, to change the status to be 1 for all customers whose status is 2:
+ *
+ * ~~~
+ * Customer::updateAll(['status' => 1], ['id' => 2]);
+ * ~~~
+ *
+ * @param array $attributes attribute values (name-value pairs) to be saved into the table
+ * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
+ * @return integer the number of rows updated
+ */
+ public static function updateAll($attributes, $condition = null)
+ {
+ if (empty($attributes)) {
+ return 0;
+ }
+ $db = static::getDb();
+ $n = 0;
+ foreach (static::fetchPks($condition) as $pk) {
+ $newPk = $pk;
+ $pk = static::buildKey($pk);
+ $key = static::keyPrefix() . ':a:' . $pk;
+ // save attributes
+ $args = [$key];
+ foreach ($attributes as $attribute => $value) {
+ if (isset($newPk[$attribute])) {
+ $newPk[$attribute] = $value;
+ }
+ $args[] = $attribute;
+ $args[] = $value;
+ }
+ $newPk = static::buildKey($newPk);
+ $newKey = static::keyPrefix() . ':a:' . $newPk;
+ // rename index if pk changed
+ if ($newPk != $pk) {
+ $db->executeCommand('MULTI');
+ $db->executeCommand('HMSET', $args);
+ $db->executeCommand('LINSERT', [static::keyPrefix(), 'AFTER', $pk, $newPk]);
+ $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
+ $db->executeCommand('RENAME', [$key, $newKey]);
+ $db->executeCommand('EXEC');
+ } else {
+ $db->executeCommand('HMSET', $args);
+ }
+ $n++;
+ }
+
+ return $n;
+ }
+
+ /**
+ * Updates the whole table using the provided counter changes and conditions.
+ * For example, to increment all customers' age by 1,
+ *
+ * ~~~
+ * Customer::updateAllCounters(['age' => 1]);
+ * ~~~
+ *
+ * @param array $counters the counters to be updated (attribute name => increment value).
+ * Use negative values if you want to decrement the counters.
+ * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
+ * @return integer the number of rows updated
+ */
+ public static function updateAllCounters($counters, $condition = null)
+ {
+ if (empty($counters)) {
+ return 0;
+ }
+ $db = static::getDb();
+ $n = 0;
+ foreach (static::fetchPks($condition) as $pk) {
+ $key = static::keyPrefix() . ':a:' . static::buildKey($pk);
+ foreach ($counters as $attribute => $value) {
+ $db->executeCommand('HINCRBY', [$key, $attribute, $value]);
+ }
+ $n++;
+ }
+
+ return $n;
+ }
- $key = static::keyPrefix() . ':a:' . static::buildKey($pk);
- // save attributes
- $args = [$key];
- foreach ($values as $attribute => $value) {
- $args[] = $attribute;
- $args[] = $value;
- }
- $db->executeCommand('HMSET', $args);
+ /**
+ * Deletes rows in the table using the provided conditions.
+ * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
+ *
+ * For example, to delete all customers whose status is 3:
+ *
+ * ~~~
+ * Customer::deleteAll(['status' => 3]);
+ * ~~~
+ *
+ * @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
+ * Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
+ * @return integer the number of rows deleted
+ */
+ public static function deleteAll($condition = null)
+ {
+ $db = static::getDb();
+ $attributeKeys = [];
+ $pks = static::fetchPks($condition);
+ $db->executeCommand('MULTI');
+ foreach ($pks as $pk) {
+ $pk = static::buildKey($pk);
+ $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
+ $attributeKeys[] = static::keyPrefix() . ':a:' . $pk;
+ }
+ if (empty($attributeKeys)) {
+ $db->executeCommand('EXEC');
- $this->setOldAttributes($values);
- $this->afterSave(true);
- return true;
- }
- return false;
- }
+ return 0;
+ }
+ $db->executeCommand('DEL', $attributeKeys);
+ $result = $db->executeCommand('EXEC');
- /**
- * Updates the whole table using the provided attribute values and conditions.
- * For example, to change the status to be 1 for all customers whose status is 2:
- *
- * ~~~
- * Customer::updateAll(['status' => 1], ['id' => 2]);
- * ~~~
- *
- * @param array $attributes attribute values (name-value pairs) to be saved into the table
- * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
- * Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
- * @return integer the number of rows updated
- */
- public static function updateAll($attributes, $condition = null)
- {
- if (empty($attributes)) {
- return 0;
- }
- $db = static::getDb();
- $n = 0;
- foreach (static::fetchPks($condition) as $pk) {
- $newPk = $pk;
- $pk = static::buildKey($pk);
- $key = static::keyPrefix() . ':a:' . $pk;
- // save attributes
- $args = [$key];
- foreach ($attributes as $attribute => $value) {
- if (isset($newPk[$attribute])) {
- $newPk[$attribute] = $value;
- }
- $args[] = $attribute;
- $args[] = $value;
- }
- $newPk = static::buildKey($newPk);
- $newKey = static::keyPrefix() . ':a:' . $newPk;
- // rename index if pk changed
- if ($newPk != $pk) {
- $db->executeCommand('MULTI');
- $db->executeCommand('HMSET', $args);
- $db->executeCommand('LINSERT', [static::keyPrefix(), 'AFTER', $pk, $newPk]);
- $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
- $db->executeCommand('RENAME', [$key, $newKey]);
- $db->executeCommand('EXEC');
- } else {
- $db->executeCommand('HMSET', $args);
- }
- $n++;
- }
- return $n;
- }
+ return end($result);
+ }
- /**
- * Updates the whole table using the provided counter changes and conditions.
- * For example, to increment all customers' age by 1,
- *
- * ~~~
- * Customer::updateAllCounters(['age' => 1]);
- * ~~~
- *
- * @param array $counters the counters to be updated (attribute name => increment value).
- * Use negative values if you want to decrement the counters.
- * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
- * Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
- * @return integer the number of rows updated
- */
- public static function updateAllCounters($counters, $condition = null)
- {
- if (empty($counters)) {
- return 0;
- }
- $db = static::getDb();
- $n = 0;
- foreach (static::fetchPks($condition) as $pk) {
- $key = static::keyPrefix() . ':a:' . static::buildKey($pk);
- foreach ($counters as $attribute => $value) {
- $db->executeCommand('HINCRBY', [$key, $attribute, $value]);
- }
- $n++;
- }
- return $n;
- }
+ private static function fetchPks($condition)
+ {
+ $query = static::createQuery();
+ $query->where($condition);
+ $records = $query->asArray()->all(); // TODO limit fetched columns to pk
+ $primaryKey = static::primaryKey();
- /**
- * Deletes rows in the table using the provided conditions.
- * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
- *
- * For example, to delete all customers whose status is 3:
- *
- * ~~~
- * Customer::deleteAll(['status' => 3]);
- * ~~~
- *
- * @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
- * Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
- * @return integer the number of rows deleted
- */
- public static function deleteAll($condition = null)
- {
- $db = static::getDb();
- $attributeKeys = [];
- $pks = static::fetchPks($condition);
- $db->executeCommand('MULTI');
- foreach ($pks as $pk) {
- $pk = static::buildKey($pk);
- $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
- $attributeKeys[] = static::keyPrefix() . ':a:' . $pk;
- }
- if (empty($attributeKeys)) {
- $db->executeCommand('EXEC');
- return 0;
- }
- $db->executeCommand('DEL', $attributeKeys);
- $result = $db->executeCommand('EXEC');
- return end($result);
- }
+ $pks = [];
+ foreach ($records as $record) {
+ $pk = [];
+ foreach ($primaryKey as $key) {
+ $pk[$key] = $record[$key];
+ }
+ $pks[] = $pk;
+ }
- private static function fetchPks($condition)
- {
- $query = static::createQuery();
- $query->where($condition);
- $records = $query->asArray()->all(); // TODO limit fetched columns to pk
- $primaryKey = static::primaryKey();
+ return $pks;
+ }
- $pks = [];
- foreach ($records as $record) {
- $pk = [];
- foreach ($primaryKey as $key) {
- $pk[$key] = $record[$key];
- }
- $pks[] = $pk;
- }
- return $pks;
- }
+ /**
+ * Builds a normalized key from a given primary key value.
+ *
+ * @param mixed $key the key to be normalized
+ * @return string the generated key
+ */
+ public static function buildKey($key)
+ {
+ if (is_numeric($key)) {
+ return $key;
+ } elseif (is_string($key)) {
+ return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
+ } elseif (is_array($key)) {
+ if (count($key) == 1) {
+ return self::buildKey(reset($key));
+ }
+ ksort($key); // ensure order is always the same
+ $isNumeric = true;
+ foreach ($key as $value) {
+ if (!is_numeric($value)) {
+ $isNumeric = false;
+ }
+ }
+ if ($isNumeric) {
+ return implode('-', $key);
+ }
+ }
- /**
- * Builds a normalized key from a given primary key value.
- *
- * @param mixed $key the key to be normalized
- * @return string the generated key
- */
- public static function buildKey($key)
- {
- if (is_numeric($key)) {
- return $key;
- } elseif (is_string($key)) {
- return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
- } elseif (is_array($key)) {
- if (count($key) == 1) {
- return self::buildKey(reset($key));
- }
- ksort($key); // ensure order is always the same
- $isNumeric = true;
- foreach ($key as $value) {
- if (!is_numeric($value)) {
- $isNumeric = false;
- }
- }
- if ($isNumeric) {
- return implode('-', $key);
- }
- }
- return md5(json_encode($key));
- }
+ return md5(json_encode($key));
+ }
}
diff --git a/extensions/redis/Cache.php b/extensions/redis/Cache.php
index 18ac8d7aa67..02438ae1cbe 100644
--- a/extensions/redis/Cache.php
+++ b/extensions/redis/Cache.php
@@ -58,147 +58,150 @@
*/
class Cache extends \yii\caching\Cache
{
- /**
- * @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
- * This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
- * redis connection as an application component.
- * After the Cache object is created, if you want to change this property, you should only assign it
- * with a Redis [[Connection]] object.
- */
- public $redis = 'redis';
-
-
- /**
- * Initializes the redis Cache component.
- * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
- * @throws InvalidConfigException if [[redis]] is invalid.
- */
- public function init()
- {
- parent::init();
- if (is_string($this->redis)) {
- $this->redis = Yii::$app->getComponent($this->redis);
- } elseif (is_array($this->redis)) {
- if (!isset($this->redis['class'])) {
- $this->redis['class'] = Connection::className();
- }
- $this->redis = Yii::createObject($this->redis);
- }
- if (!$this->redis instanceof Connection) {
- throw new InvalidConfigException("Cache::redis must be either a Redis connection instance or the application component ID of a Redis connection.");
- }
- }
-
- /**
- * Checks whether a specified key exists in the cache.
- * This can be faster than getting the value from the cache if the data is big.
- * Note that this method does not check whether the dependency associated
- * with the cached data, if there is any, has changed. So a call to [[get]]
- * may return false while exists returns true.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
- */
- public function exists($key)
- {
- return (bool) $this->redis->executeCommand('EXISTS', [$this->buildKey($key)]);
- }
-
- /**
- * @inheritdoc
- */
- protected function getValue($key)
- {
- return $this->redis->executeCommand('GET', [$key]);
- }
-
- /**
- * @inheritdoc
- */
- protected function getValues($keys)
- {
- $response = $this->redis->executeCommand('MGET', $keys);
- $result = [];
- $i = 0;
- foreach ($keys as $key) {
- $result[$key] = $response[$i++];
- }
- return $result;
- }
-
- /**
- * @inheritdoc
- */
- protected function setValue($key, $value, $expire)
- {
- if ($expire == 0) {
- return (bool) $this->redis->executeCommand('SET', [$key, $value]);
- } else {
- $expire = (int) ($expire * 1000);
- return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire]);
- }
- }
-
- /**
- * @inheritdoc
- */
- protected function setValues($data, $expire)
- {
- $args = [];
- foreach ($data as $key => $value) {
- $args[] = $key;
- $args[] = $value;
- }
-
- $failedKeys = [];
- if ($expire == 0) {
- $this->redis->executeCommand('MSET', $args);
- } else {
- $expire = (int) ($expire * 1000);
- $this->redis->executeCommand('MULTI');
- $this->redis->executeCommand('MSET', $args);
- $index = [];
- foreach ($data as $key => $value) {
- $this->redis->executeCommand('PEXPIRE', [$key, $expire]);
- $index[] = $key;
- }
- $result = $this->redis->executeCommand('EXEC');
- array_shift($result);
- foreach ($result as $i => $r) {
- if ($r != 1) {
- $failedKeys[] = $index[$i];
- }
- }
- }
- return $failedKeys;
- }
-
- /**
- * @inheritdoc
- */
- protected function addValue($key, $value, $expire)
- {
- if ($expire == 0) {
- return (bool) $this->redis->executeCommand('SET', [$key, $value, 'NX']);
- } else {
- $expire = (int) ($expire * 1000);
- return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']);
- }
- }
-
- /**
- * @inheritdoc
- */
- protected function deleteValue($key)
- {
- return (bool) $this->redis->executeCommand('DEL', [$key]);
- }
-
- /**
- * @inheritdoc
- */
- protected function flushValues()
- {
- return $this->redis->executeCommand('FLUSHDB');
- }
+ /**
+ * @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
+ * This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
+ * redis connection as an application component.
+ * After the Cache object is created, if you want to change this property, you should only assign it
+ * with a Redis [[Connection]] object.
+ */
+ public $redis = 'redis';
+
+ /**
+ * Initializes the redis Cache component.
+ * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
+ * @throws InvalidConfigException if [[redis]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->redis)) {
+ $this->redis = Yii::$app->getComponent($this->redis);
+ } elseif (is_array($this->redis)) {
+ if (!isset($this->redis['class'])) {
+ $this->redis['class'] = Connection::className();
+ }
+ $this->redis = Yii::createObject($this->redis);
+ }
+ if (!$this->redis instanceof Connection) {
+ throw new InvalidConfigException("Cache::redis must be either a Redis connection instance or the application component ID of a Redis connection.");
+ }
+ }
+
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ return (bool) $this->redis->executeCommand('EXISTS', [$this->buildKey($key)]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getValue($key)
+ {
+ return $this->redis->executeCommand('GET', [$key]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getValues($keys)
+ {
+ $response = $this->redis->executeCommand('MGET', $keys);
+ $result = [];
+ $i = 0;
+ foreach ($keys as $key) {
+ $result[$key] = $response[$i++];
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ if ($expire == 0) {
+ return (bool) $this->redis->executeCommand('SET', [$key, $value]);
+ } else {
+ $expire = (int) ($expire * 1000);
+
+ return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire]);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function setValues($data, $expire)
+ {
+ $args = [];
+ foreach ($data as $key => $value) {
+ $args[] = $key;
+ $args[] = $value;
+ }
+
+ $failedKeys = [];
+ if ($expire == 0) {
+ $this->redis->executeCommand('MSET', $args);
+ } else {
+ $expire = (int) ($expire * 1000);
+ $this->redis->executeCommand('MULTI');
+ $this->redis->executeCommand('MSET', $args);
+ $index = [];
+ foreach ($data as $key => $value) {
+ $this->redis->executeCommand('PEXPIRE', [$key, $expire]);
+ $index[] = $key;
+ }
+ $result = $this->redis->executeCommand('EXEC');
+ array_shift($result);
+ foreach ($result as $i => $r) {
+ if ($r != 1) {
+ $failedKeys[] = $index[$i];
+ }
+ }
+ }
+
+ return $failedKeys;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ if ($expire == 0) {
+ return (bool) $this->redis->executeCommand('SET', [$key, $value, 'NX']);
+ } else {
+ $expire = (int) ($expire * 1000);
+
+ return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function deleteValue($key)
+ {
+ return (bool) $this->redis->executeCommand('DEL', [$key]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function flushValues()
+ {
+ return $this->redis->executeCommand('FLUSHDB');
+ }
}
diff --git a/extensions/redis/Connection.php b/extensions/redis/Connection.php
index c7296bcd986..44bbe58fc91 100644
--- a/extensions/redis/Connection.php
+++ b/extensions/redis/Connection.php
@@ -35,371 +35,374 @@
*/
class Connection extends Component
{
- /**
- * @event Event an event that is triggered after a DB connection is established
- */
- const EVENT_AFTER_OPEN = 'afterOpen';
+ /**
+ * @event Event an event that is triggered after a DB connection is established
+ */
+ const EVENT_AFTER_OPEN = 'afterOpen';
- /**
- * @var string the hostname or ip address to use for connecting to the redis server. Defaults to 'localhost'.
- */
- public $hostname = 'localhost';
- /**
- * @var integer the port to use for connecting to the redis server. Default port is 6379.
- */
- public $port = 6379;
- /**
- * @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is send.
- * See http://redis.io/commands/auth
- */
- public $password;
- /**
- * @var integer the redis database to use. This is an integer value starting from 0. Defaults to 0.
- */
- public $database = 0;
- /**
- * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
- */
- public $connectionTimeout = null;
- /**
- * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
- */
- public $dataTimeout = null;
+ /**
+ * @var string the hostname or ip address to use for connecting to the redis server. Defaults to 'localhost'.
+ */
+ public $hostname = 'localhost';
+ /**
+ * @var integer the port to use for connecting to the redis server. Default port is 6379.
+ */
+ public $port = 6379;
+ /**
+ * @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is send.
+ * See http://redis.io/commands/auth
+ */
+ public $password;
+ /**
+ * @var integer the redis database to use. This is an integer value starting from 0. Defaults to 0.
+ */
+ public $database = 0;
+ /**
+ * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
+ */
+ public $connectionTimeout = null;
+ /**
+ * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
+ */
+ public $dataTimeout = null;
- /**
- * @var array List of available redis commands http://redis.io/commands
- */
- public $redisCommands = [
- 'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available
- 'BRPOPLPUSH', // source destination timeout Pop a value from a list, push it to another list and return it; or block until one is available
- 'CLIENT KILL', // ip:port Kill the connection of a client
- 'CLIENT LIST', // Get the list of client connections
- 'CLIENT GETNAME', // Get the current connection name
- 'CLIENT SETNAME', // connection-name Set the current connection name
- 'CONFIG GET', // parameter Get the value of a configuration parameter
- 'CONFIG SET', // parameter value Set a configuration parameter to the given value
- 'CONFIG RESETSTAT', // Reset the stats returned by INFO
- 'DBSIZE', // Return the number of keys in the selected database
- 'DEBUG OBJECT', // key Get debugging information about a key
- 'DEBUG SEGFAULT', // Make the server crash
- 'DECR', // key Decrement the integer value of a key by one
- 'DECRBY', // key decrement Decrement the integer value of a key by the given number
- 'DEL', // key [key ...] Delete a key
- 'DISCARD', // Discard all commands issued after MULTI
- 'DUMP', // key Return a serialized version of the value stored at the specified key.
- 'ECHO', // message Echo the given string
- 'EVAL', // script numkeys key [key ...] arg [arg ...] Execute a Lua script server side
- 'EVALSHA', // sha1 numkeys key [key ...] arg [arg ...] Execute a Lua script server side
- 'EXEC', // Execute all commands issued after MULTI
- 'EXISTS', // key Determine if a key exists
- 'EXPIRE', // key seconds Set a key's time to live in seconds
- 'EXPIREAT', // key timestamp Set the expiration for a key as a UNIX timestamp
- 'FLUSHALL', // Remove all keys from all databases
- 'FLUSHDB', // Remove all keys from the current database
- 'GET', // key Get the value of a key
- 'GETBIT', // key offset Returns the bit value at offset in the string value stored at key
- 'GETRANGE', // key start end Get a substring of the string stored at a key
- 'GETSET', // key value Set the string value of a key and return its old value
- 'HDEL', // key field [field ...] Delete one or more hash fields
- 'HEXISTS', // key field Determine if a hash field exists
- 'HGET', // key field Get the value of a hash field
- 'HGETALL', // key Get all the fields and values in a hash
- 'HINCRBY', // key field increment Increment the integer value of a hash field by the given number
- 'HINCRBYFLOAT', // key field increment Increment the float value of a hash field by the given amount
- 'HKEYS', // key Get all the fields in a hash
- 'HLEN', // key Get the number of fields in a hash
- 'HMGET', // key field [field ...] Get the values of all the given hash fields
- 'HMSET', // key field value [field value ...] Set multiple hash fields to multiple values
- 'HSET', // key field value Set the string value of a hash field
- 'HSETNX', // key field value Set the value of a hash field, only if the field does not exist
- 'HVALS', // key Get all the values in a hash
- 'INCR', // key Increment the integer value of a key by one
- 'INCRBY', // key increment Increment the integer value of a key by the given amount
- 'INCRBYFLOAT', // key increment Increment the float value of a key by the given amount
- 'INFO', // [section] Get information and statistics about the server
- 'KEYS', // pattern Find all keys matching the given pattern
- 'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk
- 'LINDEX', // key index Get an element from a list by its index
- 'LINSERT', // key BEFORE|AFTER pivot value Insert an element before or after another element in a list
- 'LLEN', // key Get the length of a list
- 'LPOP', // key Remove and get the first element in a list
- 'LPUSH', // key value [value ...] Prepend one or multiple values to a list
- 'LPUSHX', // key value Prepend a value to a list, only if the list exists
- 'LRANGE', // key start stop Get a range of elements from a list
- 'LREM', // key count value Remove elements from a list
- 'LSET', // key index value Set the value of an element in a list by its index
- 'LTRIM', // key start stop Trim a list to the specified range
- 'MGET', // key [key ...] Get the values of all the given keys
- 'MIGRATE', // host port key destination-db timeout Atomically transfer a key from a Redis instance to another one.
- 'MONITOR', // Listen for all requests received by the server in real time
- 'MOVE', // key db Move a key to another database
- 'MSET', // key value [key value ...] Set multiple keys to multiple values
- 'MSETNX', // key value [key value ...] Set multiple keys to multiple values, only if none of the keys exist
- 'MULTI', // Mark the start of a transaction block
- 'OBJECT', // subcommand [arguments [arguments ...]] Inspect the internals of Redis objects
- 'PERSIST', // key Remove the expiration from a key
- 'PEXPIRE', // key milliseconds Set a key's time to live in milliseconds
- 'PEXPIREAT', // key milliseconds-timestamp Set the expiration for a key as a UNIX timestamp specified in milliseconds
- 'PING', // Ping the server
- 'PSETEX', // key milliseconds value Set the value and expiration in milliseconds of a key
- 'PSUBSCRIBE', // pattern [pattern ...] Listen for messages published to channels matching the given patterns
- 'PTTL', // key Get the time to live for a key in milliseconds
- 'PUBLISH', // channel message Post a message to a channel
- 'PUNSUBSCRIBE', // [pattern [pattern ...]] Stop listening for messages posted to channels matching the given patterns
- 'QUIT', // Close the connection
- 'RANDOMKEY', // Return a random key from the keyspace
- 'RENAME', // key newkey Rename a key
- 'RENAMENX', // key newkey Rename a key, only if the new key does not exist
- 'RESTORE', // key ttl serialized-value Create a key using the provided serialized value, previously obtained using DUMP.
- 'RPOP', // key Remove and get the last element in a list
- 'RPOPLPUSH', // source destination Remove the last element in a list, append it to another list and return it
- 'RPUSH', // key value [value ...] Append one or multiple values to a list
- 'RPUSHX', // key value Append a value to a list, only if the list exists
- 'SADD', // key member [member ...] Add one or more members to a set
- 'SAVE', // Synchronously save the dataset to disk
- 'SCARD', // key Get the number of members in a set
- 'SCRIPT EXISTS', // script [script ...] Check existence of scripts in the script cache.
- 'SCRIPT FLUSH', // Remove all the scripts from the script cache.
- 'SCRIPT KILL', // Kill the script currently in execution.
- 'SCRIPT LOAD', // script Load the specified Lua script into the script cache.
- 'SDIFF', // key [key ...] Subtract multiple sets
- 'SDIFFSTORE', // destination key [key ...] Subtract multiple sets and store the resulting set in a key
- 'SELECT', // index Change the selected database for the current connection
- 'SET', // key value Set the string value of a key
- 'SETBIT', // key offset value Sets or clears the bit at offset in the string value stored at key
- 'SETEX', // key seconds value Set the value and expiration of a key
- 'SETNX', // key value Set the value of a key, only if the key does not exist
- 'SETRANGE', // key offset value Overwrite part of a string at key starting at the specified offset
- 'SHUTDOWN', // [NOSAVE] [SAVE] Synchronously save the dataset to disk and then shut down the server
- 'SINTER', // key [key ...] Intersect multiple sets
- 'SINTERSTORE', // destination key [key ...] Intersect multiple sets and store the resulting set in a key
- 'SISMEMBER', // key member Determine if a given value is a member of a set
- 'SLAVEOF', // host port Make the server a slave of another instance, or promote it as master
- 'SLOWLOG', // subcommand [argument] Manages the Redis slow queries log
- 'SMEMBERS', // key Get all the members in a set
- 'SMOVE', // source destination member Move a member from one set to another
- 'SORT', // key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] Sort the elements in a list, set or sorted set
- 'SPOP', // key Remove and return a random member from a set
- 'SRANDMEMBER', // key [count] Get one or multiple random members from a set
- 'SREM', // key member [member ...] Remove one or more members from a set
- 'STRLEN', // key Get the length of the value stored in a key
- 'SUBSCRIBE', // channel [channel ...] Listen for messages published to the given channels
- 'SUNION', // key [key ...] Add multiple sets
- 'SUNIONSTORE', // destination key [key ...] Add multiple sets and store the resulting set in a key
- 'SYNC', // Internal command used for replication
- 'TIME', // Return the current server time
- 'TTL', // key Get the time to live for a key
- 'TYPE', // key Determine the type stored at key
- 'UNSUBSCRIBE', // [channel [channel ...]] Stop listening for messages posted to the given channels
- 'UNWATCH', // Forget about all watched keys
- 'WATCH', // key [key ...] Watch the given keys to determine execution of the MULTI/EXEC block
- 'ZADD', // key score member [score member ...] Add one or more members to a sorted set, or update its score if it already exists
- 'ZCARD', // key Get the number of members in a sorted set
- 'ZCOUNT', // key min max Count the members in a sorted set with scores within the given values
- 'ZINCRBY', // key increment member Increment the score of a member in a sorted set
- 'ZINTERSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Intersect multiple sorted sets and store the resulting sorted set in a new key
- 'ZRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index
- 'ZRANGEBYSCORE', // key min max [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score
- 'ZRANK', // key member Determine the index of a member in a sorted set
- 'ZREM', // key member [member ...] Remove one or more members from a sorted set
- 'ZREMRANGEBYRANK', // key start stop Remove all members in a sorted set within the given indexes
- 'ZREMRANGEBYSCORE', // key min max Remove all members in a sorted set within the given scores
- 'ZREVRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index, with scores ordered from high to low
- 'ZREVRANGEBYSCORE', // key max min [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score, with scores ordered from high to low
- 'ZREVRANK', // key member Determine the index of a member in a sorted set, with scores ordered from high to low
- 'ZSCORE', // key member Get the score associated with the given member in a sorted set
- 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key
- ];
- /**
- * @var resource redis socket connection
- */
- private $_socket;
+ /**
+ * @var array List of available redis commands http://redis.io/commands
+ */
+ public $redisCommands = [
+ 'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available
+ 'BRPOPLPUSH', // source destination timeout Pop a value from a list, push it to another list and return it; or block until one is available
+ 'CLIENT KILL', // ip:port Kill the connection of a client
+ 'CLIENT LIST', // Get the list of client connections
+ 'CLIENT GETNAME', // Get the current connection name
+ 'CLIENT SETNAME', // connection-name Set the current connection name
+ 'CONFIG GET', // parameter Get the value of a configuration parameter
+ 'CONFIG SET', // parameter value Set a configuration parameter to the given value
+ 'CONFIG RESETSTAT', // Reset the stats returned by INFO
+ 'DBSIZE', // Return the number of keys in the selected database
+ 'DEBUG OBJECT', // key Get debugging information about a key
+ 'DEBUG SEGFAULT', // Make the server crash
+ 'DECR', // key Decrement the integer value of a key by one
+ 'DECRBY', // key decrement Decrement the integer value of a key by the given number
+ 'DEL', // key [key ...] Delete a key
+ 'DISCARD', // Discard all commands issued after MULTI
+ 'DUMP', // key Return a serialized version of the value stored at the specified key.
+ 'ECHO', // message Echo the given string
+ 'EVAL', // script numkeys key [key ...] arg [arg ...] Execute a Lua script server side
+ 'EVALSHA', // sha1 numkeys key [key ...] arg [arg ...] Execute a Lua script server side
+ 'EXEC', // Execute all commands issued after MULTI
+ 'EXISTS', // key Determine if a key exists
+ 'EXPIRE', // key seconds Set a key's time to live in seconds
+ 'EXPIREAT', // key timestamp Set the expiration for a key as a UNIX timestamp
+ 'FLUSHALL', // Remove all keys from all databases
+ 'FLUSHDB', // Remove all keys from the current database
+ 'GET', // key Get the value of a key
+ 'GETBIT', // key offset Returns the bit value at offset in the string value stored at key
+ 'GETRANGE', // key start end Get a substring of the string stored at a key
+ 'GETSET', // key value Set the string value of a key and return its old value
+ 'HDEL', // key field [field ...] Delete one or more hash fields
+ 'HEXISTS', // key field Determine if a hash field exists
+ 'HGET', // key field Get the value of a hash field
+ 'HGETALL', // key Get all the fields and values in a hash
+ 'HINCRBY', // key field increment Increment the integer value of a hash field by the given number
+ 'HINCRBYFLOAT', // key field increment Increment the float value of a hash field by the given amount
+ 'HKEYS', // key Get all the fields in a hash
+ 'HLEN', // key Get the number of fields in a hash
+ 'HMGET', // key field [field ...] Get the values of all the given hash fields
+ 'HMSET', // key field value [field value ...] Set multiple hash fields to multiple values
+ 'HSET', // key field value Set the string value of a hash field
+ 'HSETNX', // key field value Set the value of a hash field, only if the field does not exist
+ 'HVALS', // key Get all the values in a hash
+ 'INCR', // key Increment the integer value of a key by one
+ 'INCRBY', // key increment Increment the integer value of a key by the given amount
+ 'INCRBYFLOAT', // key increment Increment the float value of a key by the given amount
+ 'INFO', // [section] Get information and statistics about the server
+ 'KEYS', // pattern Find all keys matching the given pattern
+ 'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk
+ 'LINDEX', // key index Get an element from a list by its index
+ 'LINSERT', // key BEFORE|AFTER pivot value Insert an element before or after another element in a list
+ 'LLEN', // key Get the length of a list
+ 'LPOP', // key Remove and get the first element in a list
+ 'LPUSH', // key value [value ...] Prepend one or multiple values to a list
+ 'LPUSHX', // key value Prepend a value to a list, only if the list exists
+ 'LRANGE', // key start stop Get a range of elements from a list
+ 'LREM', // key count value Remove elements from a list
+ 'LSET', // key index value Set the value of an element in a list by its index
+ 'LTRIM', // key start stop Trim a list to the specified range
+ 'MGET', // key [key ...] Get the values of all the given keys
+ 'MIGRATE', // host port key destination-db timeout Atomically transfer a key from a Redis instance to another one.
+ 'MONITOR', // Listen for all requests received by the server in real time
+ 'MOVE', // key db Move a key to another database
+ 'MSET', // key value [key value ...] Set multiple keys to multiple values
+ 'MSETNX', // key value [key value ...] Set multiple keys to multiple values, only if none of the keys exist
+ 'MULTI', // Mark the start of a transaction block
+ 'OBJECT', // subcommand [arguments [arguments ...]] Inspect the internals of Redis objects
+ 'PERSIST', // key Remove the expiration from a key
+ 'PEXPIRE', // key milliseconds Set a key's time to live in milliseconds
+ 'PEXPIREAT', // key milliseconds-timestamp Set the expiration for a key as a UNIX timestamp specified in milliseconds
+ 'PING', // Ping the server
+ 'PSETEX', // key milliseconds value Set the value and expiration in milliseconds of a key
+ 'PSUBSCRIBE', // pattern [pattern ...] Listen for messages published to channels matching the given patterns
+ 'PTTL', // key Get the time to live for a key in milliseconds
+ 'PUBLISH', // channel message Post a message to a channel
+ 'PUNSUBSCRIBE', // [pattern [pattern ...]] Stop listening for messages posted to channels matching the given patterns
+ 'QUIT', // Close the connection
+ 'RANDOMKEY', // Return a random key from the keyspace
+ 'RENAME', // key newkey Rename a key
+ 'RENAMENX', // key newkey Rename a key, only if the new key does not exist
+ 'RESTORE', // key ttl serialized-value Create a key using the provided serialized value, previously obtained using DUMP.
+ 'RPOP', // key Remove and get the last element in a list
+ 'RPOPLPUSH', // source destination Remove the last element in a list, append it to another list and return it
+ 'RPUSH', // key value [value ...] Append one or multiple values to a list
+ 'RPUSHX', // key value Append a value to a list, only if the list exists
+ 'SADD', // key member [member ...] Add one or more members to a set
+ 'SAVE', // Synchronously save the dataset to disk
+ 'SCARD', // key Get the number of members in a set
+ 'SCRIPT EXISTS', // script [script ...] Check existence of scripts in the script cache.
+ 'SCRIPT FLUSH', // Remove all the scripts from the script cache.
+ 'SCRIPT KILL', // Kill the script currently in execution.
+ 'SCRIPT LOAD', // script Load the specified Lua script into the script cache.
+ 'SDIFF', // key [key ...] Subtract multiple sets
+ 'SDIFFSTORE', // destination key [key ...] Subtract multiple sets and store the resulting set in a key
+ 'SELECT', // index Change the selected database for the current connection
+ 'SET', // key value Set the string value of a key
+ 'SETBIT', // key offset value Sets or clears the bit at offset in the string value stored at key
+ 'SETEX', // key seconds value Set the value and expiration of a key
+ 'SETNX', // key value Set the value of a key, only if the key does not exist
+ 'SETRANGE', // key offset value Overwrite part of a string at key starting at the specified offset
+ 'SHUTDOWN', // [NOSAVE] [SAVE] Synchronously save the dataset to disk and then shut down the server
+ 'SINTER', // key [key ...] Intersect multiple sets
+ 'SINTERSTORE', // destination key [key ...] Intersect multiple sets and store the resulting set in a key
+ 'SISMEMBER', // key member Determine if a given value is a member of a set
+ 'SLAVEOF', // host port Make the server a slave of another instance, or promote it as master
+ 'SLOWLOG', // subcommand [argument] Manages the Redis slow queries log
+ 'SMEMBERS', // key Get all the members in a set
+ 'SMOVE', // source destination member Move a member from one set to another
+ 'SORT', // key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] Sort the elements in a list, set or sorted set
+ 'SPOP', // key Remove and return a random member from a set
+ 'SRANDMEMBER', // key [count] Get one or multiple random members from a set
+ 'SREM', // key member [member ...] Remove one or more members from a set
+ 'STRLEN', // key Get the length of the value stored in a key
+ 'SUBSCRIBE', // channel [channel ...] Listen for messages published to the given channels
+ 'SUNION', // key [key ...] Add multiple sets
+ 'SUNIONSTORE', // destination key [key ...] Add multiple sets and store the resulting set in a key
+ 'SYNC', // Internal command used for replication
+ 'TIME', // Return the current server time
+ 'TTL', // key Get the time to live for a key
+ 'TYPE', // key Determine the type stored at key
+ 'UNSUBSCRIBE', // [channel [channel ...]] Stop listening for messages posted to the given channels
+ 'UNWATCH', // Forget about all watched keys
+ 'WATCH', // key [key ...] Watch the given keys to determine execution of the MULTI/EXEC block
+ 'ZADD', // key score member [score member ...] Add one or more members to a sorted set, or update its score if it already exists
+ 'ZCARD', // key Get the number of members in a sorted set
+ 'ZCOUNT', // key min max Count the members in a sorted set with scores within the given values
+ 'ZINCRBY', // key increment member Increment the score of a member in a sorted set
+ 'ZINTERSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Intersect multiple sorted sets and store the resulting sorted set in a new key
+ 'ZRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index
+ 'ZRANGEBYSCORE', // key min max [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score
+ 'ZRANK', // key member Determine the index of a member in a sorted set
+ 'ZREM', // key member [member ...] Remove one or more members from a sorted set
+ 'ZREMRANGEBYRANK', // key start stop Remove all members in a sorted set within the given indexes
+ 'ZREMRANGEBYSCORE', // key min max Remove all members in a sorted set within the given scores
+ 'ZREVRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index, with scores ordered from high to low
+ 'ZREVRANGEBYSCORE', // key max min [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score, with scores ordered from high to low
+ 'ZREVRANK', // key member Determine the index of a member in a sorted set, with scores ordered from high to low
+ 'ZSCORE', // key member Get the score associated with the given member in a sorted set
+ 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key
+ ];
+ /**
+ * @var resource redis socket connection
+ */
+ private $_socket;
- /**
- * Closes the connection when this component is being serialized.
- * @return array
- */
- public function __sleep()
- {
- $this->close();
- return array_keys(get_object_vars($this));
- }
+ /**
+ * Closes the connection when this component is being serialized.
+ * @return array
+ */
+ public function __sleep()
+ {
+ $this->close();
- /**
- * Returns a value indicating whether the DB connection is established.
- * @return boolean whether the DB connection is established
- */
- public function getIsActive()
- {
- return $this->_socket !== null;
- }
+ return array_keys(get_object_vars($this));
+ }
- /**
- * Establishes a DB connection.
- * It does nothing if a DB connection has already been established.
- * @throws Exception if connection fails
- */
- public function open()
- {
- if ($this->_socket !== null) {
- return;
- }
- $connection = $this->hostname . ':' . $this->port . ', database=' . $this->database;
- \Yii::trace('Opening redis DB connection: ' . $connection, __CLASS__);
- $this->_socket = @stream_socket_client(
- 'tcp://' . $this->hostname . ':' . $this->port,
- $errorNumber,
- $errorDescription,
- $this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout")
- );
- if ($this->_socket) {
- if ($this->dataTimeout !== null) {
- stream_set_timeout($this->_socket, $timeout = (int)$this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
- }
- if ($this->password !== null) {
- $this->executeCommand('AUTH', [$this->password]);
- }
- $this->executeCommand('SELECT', [$this->database]);
- $this->initConnection();
- } else {
- \Yii::error("Failed to open redis DB connection ($connection): $errorNumber - $errorDescription", __CLASS__);
- $message = YII_DEBUG ? "Failed to open redis DB connection ($connection): $errorNumber - $errorDescription" : 'Failed to open DB connection.';
- throw new Exception($message, $errorDescription, (int)$errorNumber);
- }
- }
+ /**
+ * Returns a value indicating whether the DB connection is established.
+ * @return boolean whether the DB connection is established
+ */
+ public function getIsActive()
+ {
+ return $this->_socket !== null;
+ }
- /**
- * Closes the currently active DB connection.
- * It does nothing if the connection is already closed.
- */
- public function close()
- {
- if ($this->_socket !== null) {
- $connection = $this->hostname . ':' . $this->port . ', database=' . $this->database;
- \Yii::trace('Closing DB connection: ' . $connection, __CLASS__);
- $this->executeCommand('QUIT');
- stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR);
- $this->_socket = null;
- }
- }
+ /**
+ * Establishes a DB connection.
+ * It does nothing if a DB connection has already been established.
+ * @throws Exception if connection fails
+ */
+ public function open()
+ {
+ if ($this->_socket !== null) {
+ return;
+ }
+ $connection = $this->hostname . ':' . $this->port . ', database=' . $this->database;
+ \Yii::trace('Opening redis DB connection: ' . $connection, __CLASS__);
+ $this->_socket = @stream_socket_client(
+ 'tcp://' . $this->hostname . ':' . $this->port,
+ $errorNumber,
+ $errorDescription,
+ $this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout")
+ );
+ if ($this->_socket) {
+ if ($this->dataTimeout !== null) {
+ stream_set_timeout($this->_socket, $timeout = (int) $this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
+ }
+ if ($this->password !== null) {
+ $this->executeCommand('AUTH', [$this->password]);
+ }
+ $this->executeCommand('SELECT', [$this->database]);
+ $this->initConnection();
+ } else {
+ \Yii::error("Failed to open redis DB connection ($connection): $errorNumber - $errorDescription", __CLASS__);
+ $message = YII_DEBUG ? "Failed to open redis DB connection ($connection): $errorNumber - $errorDescription" : 'Failed to open DB connection.';
+ throw new Exception($message, $errorDescription, (int) $errorNumber);
+ }
+ }
- /**
- * Initializes the DB connection.
- * This method is invoked right after the DB connection is established.
- * The default implementation triggers an [[EVENT_AFTER_OPEN]] event.
- */
- protected function initConnection()
- {
- $this->trigger(self::EVENT_AFTER_OPEN);
- }
+ /**
+ * Closes the currently active DB connection.
+ * It does nothing if the connection is already closed.
+ */
+ public function close()
+ {
+ if ($this->_socket !== null) {
+ $connection = $this->hostname . ':' . $this->port . ', database=' . $this->database;
+ \Yii::trace('Closing DB connection: ' . $connection, __CLASS__);
+ $this->executeCommand('QUIT');
+ stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR);
+ $this->_socket = null;
+ }
+ }
- /**
- * Returns the name of the DB driver for the current [[dsn]].
- * @return string name of the DB driver
- */
- public function getDriverName()
- {
- return 'redis';
- }
+ /**
+ * Initializes the DB connection.
+ * This method is invoked right after the DB connection is established.
+ * The default implementation triggers an [[EVENT_AFTER_OPEN]] event.
+ */
+ protected function initConnection()
+ {
+ $this->trigger(self::EVENT_AFTER_OPEN);
+ }
- /**
- * @return LuaScriptBuilder
- */
- public function getLuaScriptBuilder()
- {
- return new LuaScriptBuilder();
- }
+ /**
+ * Returns the name of the DB driver for the current [[dsn]].
+ * @return string name of the DB driver
+ */
+ public function getDriverName()
+ {
+ return 'redis';
+ }
- /**
- *
- * @param string $name
- * @param array $params
- * @return mixed
- */
- public function __call($name, $params)
- {
- $redisCommand = strtoupper(Inflector::camel2words($name, false));
- if (in_array($redisCommand, $this->redisCommands)) {
- return $this->executeCommand($name, $params);
- } else {
- return parent::__call($name, $params);
- }
- }
+ /**
+ * @return LuaScriptBuilder
+ */
+ public function getLuaScriptBuilder()
+ {
+ return new LuaScriptBuilder();
+ }
- /**
- * Executes a redis command.
- * For a list of available commands and their parameters see http://redis.io/commands.
- *
- * @param string $name the name of the command
- * @param array $params list of parameters for the command
- * @return array|bool|null|string Dependend on the executed command this method
- * will return different data types:
- *
- * - `true` for commands that return "status reply".
- * - `string` for commands that return "integer reply"
- * as the value is in the range of a signed 64 bit integer.
- * - `string` or `null` for commands that return "bulk reply".
- * - `array` for commands that return "Multi-bulk replies".
- *
- * See [redis protocol description](http://redis.io/topics/protocol)
- * for details on the mentioned reply types.
- * @trows Exception for commands that return [error reply](http://redis.io/topics/protocol#error-reply).
- */
- public function executeCommand($name, $params = [])
- {
- $this->open();
+ /**
+ *
+ * @param string $name
+ * @param array $params
+ * @return mixed
+ */
+ public function __call($name, $params)
+ {
+ $redisCommand = strtoupper(Inflector::camel2words($name, false));
+ if (in_array($redisCommand, $this->redisCommands)) {
+ return $this->executeCommand($name, $params);
+ } else {
+ return parent::__call($name, $params);
+ }
+ }
- array_unshift($params, $name);
- $command = '*' . count($params) . "\r\n";
- foreach ($params as $arg) {
- $command .= '$' . mb_strlen($arg, '8bit') . "\r\n" . $arg . "\r\n";
- }
+ /**
+ * Executes a redis command.
+ * For a list of available commands and their parameters see http://redis.io/commands.
+ *
+ * @param string $name the name of the command
+ * @param array $params list of parameters for the command
+ * @return array|bool|null|string Dependend on the executed command this method
+ * will return different data types:
+ *
+ * - `true` for commands that return "status reply".
+ * - `string` for commands that return "integer reply"
+ * as the value is in the range of a signed 64 bit integer.
+ * - `string` or `null` for commands that return "bulk reply".
+ * - `array` for commands that return "Multi-bulk replies".
+ *
+ * See [redis protocol description](http://redis.io/topics/protocol)
+ * for details on the mentioned reply types.
+ * @trows Exception for commands that return [error reply](http://redis.io/topics/protocol#error-reply).
+ */
+ public function executeCommand($name, $params = [])
+ {
+ $this->open();
- \Yii::trace("Executing Redis Command: {$name}", __CLASS__);
- fwrite($this->_socket, $command);
+ array_unshift($params, $name);
+ $command = '*' . count($params) . "\r\n";
+ foreach ($params as $arg) {
+ $command .= '$' . mb_strlen($arg, '8bit') . "\r\n" . $arg . "\r\n";
+ }
- return $this->parseResponse(implode(' ', $params));
- }
+ \Yii::trace("Executing Redis Command: {$name}", __CLASS__);
+ fwrite($this->_socket, $command);
- private function parseResponse($command)
- {
- if (($line = fgets($this->_socket)) === false) {
- throw new Exception("Failed to read from socket.\nRedis command was: " . $command);
- }
- $type = $line[0];
- $line = mb_substr($line, 1, -2, '8bit');
- switch($type)
- {
- case '+': // Status reply
- return true;
- case '-': // Error reply
- throw new Exception("Redis error: " . $line . "\nRedis command was: " . $command);
- case ':': // Integer reply
- // no cast to int as it is in the range of a signed 64 bit integer
- return $line;
- case '$': // Bulk replies
- if ($line == '-1') {
- return null;
- }
- $length = $line + 2;
- $data = '';
- while ($length > 0) {
- if (($block = fread($this->_socket, $line + 2)) === false) {
- throw new Exception("Failed to read from socket.\nRedis command was: " . $command);
- }
- $data .= $block;
- $length -= mb_strlen($block, '8bit');
- }
- return mb_substr($data, 0, -2, '8bit');
- case '*': // Multi-bulk replies
- $count = (int) $line;
- $data = [];
- for ($i = 0; $i < $count; $i++) {
- $data[] = $this->parseResponse($command);
- }
- return $data;
- default:
- throw new Exception('Received illegal data from redis: ' . $line . "\nRedis command was: " . $command);
- }
- }
+ return $this->parseResponse(implode(' ', $params));
+ }
+
+ private function parseResponse($command)
+ {
+ if (($line = fgets($this->_socket)) === false) {
+ throw new Exception("Failed to read from socket.\nRedis command was: " . $command);
+ }
+ $type = $line[0];
+ $line = mb_substr($line, 1, -2, '8bit');
+ switch ($type) {
+ case '+': // Status reply
+
+ return true;
+ case '-': // Error reply
+ throw new Exception("Redis error: " . $line . "\nRedis command was: " . $command);
+ case ':': // Integer reply
+ // no cast to int as it is in the range of a signed 64 bit integer
+ return $line;
+ case '$': // Bulk replies
+ if ($line == '-1') {
+ return null;
+ }
+ $length = $line + 2;
+ $data = '';
+ while ($length > 0) {
+ if (($block = fread($this->_socket, $line + 2)) === false) {
+ throw new Exception("Failed to read from socket.\nRedis command was: " . $command);
+ }
+ $data .= $block;
+ $length -= mb_strlen($block, '8bit');
+ }
+
+ return mb_substr($data, 0, -2, '8bit');
+ case '*': // Multi-bulk replies
+ $count = (int) $line;
+ $data = [];
+ for ($i = 0; $i < $count; $i++) {
+ $data[] = $this->parseResponse($command);
+ }
+
+ return $data;
+ default:
+ throw new Exception('Received illegal data from redis: ' . $line . "\nRedis command was: " . $command);
+ }
+ }
}
diff --git a/extensions/redis/LuaScriptBuilder.php b/extensions/redis/LuaScriptBuilder.php
index 9fd483ef0f3..68c60c410d5 100644
--- a/extensions/redis/LuaScriptBuilder.php
+++ b/extensions/redis/LuaScriptBuilder.php
@@ -20,147 +20,154 @@
*/
class LuaScriptBuilder extends \yii\base\Object
{
- /**
- * Builds a Lua script for finding a list of records
- * @param ActiveQuery $query the query used to build the script
- * @return string
- */
- public function buildAll($query)
- {
- // TODO add support for orderBy
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
- return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL',$key .. pk)", 'pks');
- }
-
- /**
- * Builds a Lua script for finding one record
- * @param ActiveQuery $query the query used to build the script
- * @return string
- */
- public function buildOne($query)
- {
- // TODO add support for orderBy
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
- return $this->build($query, "do return redis.call('HGETALL',$key .. pk) end", 'pks');
- }
-
- /**
- * Builds a Lua script for finding a column
- * @param ActiveQuery $query the query used to build the script
- * @param string $column name of the column
- * @return string
- */
- public function buildColumn($query, $column)
- {
- // TODO add support for orderBy and indexBy
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
- return $this->build($query, "n=n+1 pks[n]=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'pks');
- }
-
- /**
- * Builds a Lua script for getting count of records
- * @param ActiveQuery $query the query used to build the script
- * @return string
- */
- public function buildCount($query)
- {
- return $this->build($query, 'n=n+1', 'n');
- }
-
- /**
- * Builds a Lua script for finding the sum of a column
- * @param ActiveQuery $query the query used to build the script
- * @param string $column name of the column
- * @return string
- */
- public function buildSum($query, $column)
- {
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
- return $this->build($query, "n=n+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'n');
- }
-
- /**
- * Builds a Lua script for finding the average of a column
- * @param ActiveQuery $query the query used to build the script
- * @param string $column name of the column
- * @return string
- */
- public function buildAverage($query, $column)
- {
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
- return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'v/n');
- }
-
- /**
- * Builds a Lua script for finding the min value of a column
- * @param ActiveQuery $query the query used to build the script
- * @param string $column name of the column
- * @return string
- */
- public function buildMin($query, $column)
- {
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
- return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or nmodelClass;
- $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
- return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n>v then v=n end", 'v');
- }
-
- /**
- * @param ActiveQuery $query the query used to build the script
- * @param string $buildResult the lua script for building the result
- * @param string $return the lua variable that should be returned
- * @throws yii\base\NotSupportedException when query contains unsupported order by condition
- * @return string
- */
- private function build($query, $buildResult, $return)
- {
- if (!empty($query->orderBy)) {
- throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.');
- }
-
- $columns = [];
- if ($query->where !== null) {
- $condition = $this->buildCondition($query->where, $columns);
- } else {
- $condition = 'true';
- }
-
- $start = $query->offset === null ? 0 : $query->offset;
- $limitCondition = 'i>' . $start . ($query->limit === null ? '' : ' and i<=' . ($start + $query->limit));
-
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $key = $this->quoteValue($modelClass::keyPrefix());
- $loadColumnValues = '';
- foreach ($columns as $column => $alias) {
- $loadColumnValues .= "local $alias=redis.call('HGET',$key .. ':a:' .. pk, '$column')\n";
- }
-
- return <<modelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
+
+ return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL',$key .. pk)", 'pks');
+ }
+
+ /**
+ * Builds a Lua script for finding one record
+ * @param ActiveQuery $query the query used to build the script
+ * @return string
+ */
+ public function buildOne($query)
+ {
+ // TODO add support for orderBy
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
+
+ return $this->build($query, "do return redis.call('HGETALL',$key .. pk) end", 'pks');
+ }
+
+ /**
+ * Builds a Lua script for finding a column
+ * @param ActiveQuery $query the query used to build the script
+ * @param string $column name of the column
+ * @return string
+ */
+ public function buildColumn($query, $column)
+ {
+ // TODO add support for orderBy and indexBy
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
+
+ return $this->build($query, "n=n+1 pks[n]=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'pks');
+ }
+
+ /**
+ * Builds a Lua script for getting count of records
+ * @param ActiveQuery $query the query used to build the script
+ * @return string
+ */
+ public function buildCount($query)
+ {
+ return $this->build($query, 'n=n+1', 'n');
+ }
+
+ /**
+ * Builds a Lua script for finding the sum of a column
+ * @param ActiveQuery $query the query used to build the script
+ * @param string $column name of the column
+ * @return string
+ */
+ public function buildSum($query, $column)
+ {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
+
+ return $this->build($query, "n=n+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'n');
+ }
+
+ /**
+ * Builds a Lua script for finding the average of a column
+ * @param ActiveQuery $query the query used to build the script
+ * @param string $column name of the column
+ * @return string
+ */
+ public function buildAverage($query, $column)
+ {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
+
+ return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'v/n');
+ }
+
+ /**
+ * Builds a Lua script for finding the min value of a column
+ * @param ActiveQuery $query the query used to build the script
+ * @param string $column name of the column
+ * @return string
+ */
+ public function buildMin($query, $column)
+ {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
+
+ return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or nmodelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
+
+ return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n>v then v=n end", 'v');
+ }
+
+ /**
+ * @param ActiveQuery $query the query used to build the script
+ * @param string $buildResult the lua script for building the result
+ * @param string $return the lua variable that should be returned
+ * @throws yii\base\NotSupportedException when query contains unsupported order by condition
+ * @return string
+ */
+ private function build($query, $buildResult, $return)
+ {
+ if (!empty($query->orderBy)) {
+ throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.');
+ }
+
+ $columns = [];
+ if ($query->where !== null) {
+ $condition = $this->buildCondition($query->where, $columns);
+ } else {
+ $condition = 'true';
+ }
+
+ $start = $query->offset === null ? 0 : $query->offset;
+ $limitCondition = 'i>' . $start . ($query->limit === null ? '' : ' and i<=' . ($start + $query->limit));
+
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $key = $this->quoteValue($modelClass::keyPrefix());
+ $loadColumnValues = '';
+ foreach ($columns as $column => $alias) {
+ $loadColumnValues .= "local $alias=redis.call('HGET',$key .. ':a:' .. pk, '$column')\n";
+ }
+
+ return << 'buildNotCondition',
- 'and' => 'buildAndCondition',
- 'or' => 'buildAndCondition',
- 'between' => 'buildBetweenCondition',
- 'not between' => 'buildBetweenCondition',
- 'in' => 'buildInCondition',
- 'not in' => 'buildInCondition',
- 'like' => 'buildLikeCondition',
- 'not like' => 'buildLikeCondition',
- 'or like' => 'buildLikeCondition',
- 'or not like' => 'buildLikeCondition',
- ];
-
- if (!is_array($condition)) {
- throw new NotSupportedException('Where condition must be an array in redis ActiveRecord.');
- }
- if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
- $operator = strtolower($condition[0]);
- if (isset($builders[$operator])) {
- $method = $builders[$operator];
- array_shift($condition);
- return $this->$method($operator, $condition, $columns);
- } else {
- throw new Exception('Found unknown operator in query: ' . $operator);
- }
- } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
- return $this->buildHashCondition($condition, $columns);
- }
- }
-
- private function buildHashCondition($condition, &$columns)
- {
- $parts = [];
- foreach ($condition as $column => $value) {
- if (is_array($value)) { // IN condition
- $parts[] = $this->buildInCondition('in', [$column, $value], $columns);
- } else {
- $column = $this->addColumn($column, $columns);
- if ($value === null) {
- $parts[] = "$column==nil";
- } elseif ($value instanceof Expression) {
- $parts[] = "$column==" . $value->expression;
- } else {
- $value = $this->quoteValue($value);
- $parts[] = "$column==$value";
- }
- }
- }
- return count($parts) === 1 ? $parts[0] : '(' . implode(') and (', $parts) . ')';
- }
-
- private function buildNotCondition($operator, $operands, &$params)
- {
- if (count($operands) != 1) {
- throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
- }
-
- $operand = reset($operands);
- if (is_array($operand)) {
- $operand = $this->buildCondition($operand, $params);
- }
- return "!($operand)";
- }
-
- private function buildAndCondition($operator, $operands, &$columns)
- {
- $parts = [];
- foreach ($operands as $operand) {
- if (is_array($operand)) {
- $operand = $this->buildCondition($operand, $columns);
- }
- if ($operand !== '') {
- $parts[] = $operand;
- }
- }
- if (!empty($parts)) {
- return '(' . implode(") $operator (", $parts) . ')';
- } else {
- return '';
- }
- }
-
- private function buildBetweenCondition($operator, $operands, &$columns)
- {
- if (!isset($operands[0], $operands[1], $operands[2])) {
- throw new Exception("Operator '$operator' requires three operands.");
- }
-
- list($column, $value1, $value2) = $operands;
-
- $value1 = $this->quoteValue($value1);
- $value2 = $this->quoteValue($value2);
- $column = $this->addColumn($column, $columns);
- return "$column >= $value1 and $column <= $value2";
- }
-
- private function buildInCondition($operator, $operands, &$columns)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new Exception("Operator '$operator' requires two operands.");
- }
-
- list($column, $values) = $operands;
-
- $values = (array)$values;
-
- if (empty($values) || $column === []) {
- return $operator === 'in' ? 'false' : 'true';
- }
-
- if (count($column) > 1) {
- return $this->buildCompositeInCondition($operator, $column, $values, $columns);
- } elseif (is_array($column)) {
- $column = reset($column);
- }
- $columnAlias = $this->addColumn($column, $columns);
- $parts = [];
- foreach ($values as $value) {
- if (is_array($value)) {
- $value = isset($value[$column]) ? $value[$column] : null;
- }
- if ($value === null) {
- $parts[] = "$columnAlias==nil";
- } elseif ($value instanceof Expression) {
- $parts[] = "$columnAlias==" . $value->expression;
- } else {
- $value = $this->quoteValue($value);
- $parts[] = "$columnAlias==$value";
- }
- }
- $operator = $operator === 'in' ? '' : 'not ';
- return "$operator(" . implode(' or ', $parts) . ')';
- }
-
- protected function buildCompositeInCondition($operator, $inColumns, $values, &$columns)
- {
- $vss = [];
- foreach ($values as $value) {
- $vs = [];
- foreach ($inColumns as $column) {
- $column = $this->addColumn($column, $columns);
- if (isset($value[$column])) {
- $vs[] = "$column==" . $this->quoteValue($value[$column]);
- } else {
- $vs[] = "$column==nil";
- }
- }
- $vss[] = '(' . implode(' and ', $vs) . ')';
- }
- $operator = $operator === 'in' ? '' : 'not ';
- return "$operator(" . implode(' or ', $vss) . ')';
- }
-
- private function buildLikeCondition($operator, $operands, &$columns)
- {
- throw new NotSupportedException('LIKE conditions are not suppoerted by redis ActiveRecord.');
- }
+ }
+
+ /**
+ * Adds a column to the list of columns to retrieve and creates an alias
+ * @param string $column the column name to add
+ * @param array $columns list of columns given by reference
+ * @return string the alias generated for the column name
+ */
+ private function addColumn($column, &$columns)
+ {
+ if (isset($columns[$column])) {
+ return $columns[$column];
+ }
+ $name = 'c' . preg_replace("/[^A-z]+/", "", $column) . count($columns);
+
+ return $columns[$column] = $name;
+ }
+
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string or int, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ */
+ private function quoteValue($str)
+ {
+ if (!is_string($str) && !is_int($str)) {
+ return $str;
+ }
+
+ return "'" . addcslashes(str_replace("'", "\\'", $str), "\000\n\r\\\032") . "'";
+ }
+
+ /**
+ * Parses the condition specification and generates the corresponding Lua expression.
+ * @param string|array $condition the condition specification. Please refer to [[ActiveQuery::where()]]
+ * on how to specify a condition.
+ * @param array $columns the list of columns and aliases to be used
+ * @return string the generated SQL expression
+ * @throws \yii\db\Exception if the condition is in bad format
+ * @throws \yii\base\NotSupportedException if the condition is not an array
+ */
+ public function buildCondition($condition, &$columns)
+ {
+ static $builders = [
+ 'not' => 'buildNotCondition',
+ 'and' => 'buildAndCondition',
+ 'or' => 'buildAndCondition',
+ 'between' => 'buildBetweenCondition',
+ 'not between' => 'buildBetweenCondition',
+ 'in' => 'buildInCondition',
+ 'not in' => 'buildInCondition',
+ 'like' => 'buildLikeCondition',
+ 'not like' => 'buildLikeCondition',
+ 'or like' => 'buildLikeCondition',
+ 'or not like' => 'buildLikeCondition',
+ ];
+
+ if (!is_array($condition)) {
+ throw new NotSupportedException('Where condition must be an array in redis ActiveRecord.');
+ }
+ if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
+ $operator = strtolower($condition[0]);
+ if (isset($builders[$operator])) {
+ $method = $builders[$operator];
+ array_shift($condition);
+
+ return $this->$method($operator, $condition, $columns);
+ } else {
+ throw new Exception('Found unknown operator in query: ' . $operator);
+ }
+ } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
+
+ return $this->buildHashCondition($condition, $columns);
+ }
+ }
+
+ private function buildHashCondition($condition, &$columns)
+ {
+ $parts = [];
+ foreach ($condition as $column => $value) {
+ if (is_array($value)) { // IN condition
+ $parts[] = $this->buildInCondition('in', [$column, $value], $columns);
+ } else {
+ $column = $this->addColumn($column, $columns);
+ if ($value === null) {
+ $parts[] = "$column==nil";
+ } elseif ($value instanceof Expression) {
+ $parts[] = "$column==" . $value->expression;
+ } else {
+ $value = $this->quoteValue($value);
+ $parts[] = "$column==$value";
+ }
+ }
+ }
+
+ return count($parts) === 1 ? $parts[0] : '(' . implode(') and (', $parts) . ')';
+ }
+
+ private function buildNotCondition($operator, $operands, &$params)
+ {
+ if (count($operands) != 1) {
+ throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
+ }
+
+ $operand = reset($operands);
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($operand, $params);
+ }
+
+ return "!($operand)";
+ }
+
+ private function buildAndCondition($operator, $operands, &$columns)
+ {
+ $parts = [];
+ foreach ($operands as $operand) {
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($operand, $columns);
+ }
+ if ($operand !== '') {
+ $parts[] = $operand;
+ }
+ }
+ if (!empty($parts)) {
+ return '(' . implode(") $operator (", $parts) . ')';
+ } else {
+ return '';
+ }
+ }
+
+ private function buildBetweenCondition($operator, $operands, &$columns)
+ {
+ if (!isset($operands[0], $operands[1], $operands[2])) {
+ throw new Exception("Operator '$operator' requires three operands.");
+ }
+
+ list($column, $value1, $value2) = $operands;
+
+ $value1 = $this->quoteValue($value1);
+ $value2 = $this->quoteValue($value2);
+ $column = $this->addColumn($column, $columns);
+
+ return "$column >= $value1 and $column <= $value2";
+ }
+
+ private function buildInCondition($operator, $operands, &$columns)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new Exception("Operator '$operator' requires two operands.");
+ }
+
+ list($column, $values) = $operands;
+
+ $values = (array) $values;
+
+ if (empty($values) || $column === []) {
+ return $operator === 'in' ? 'false' : 'true';
+ }
+
+ if (count($column) > 1) {
+ return $this->buildCompositeInCondition($operator, $column, $values, $columns);
+ } elseif (is_array($column)) {
+ $column = reset($column);
+ }
+ $columnAlias = $this->addColumn($column, $columns);
+ $parts = [];
+ foreach ($values as $value) {
+ if (is_array($value)) {
+ $value = isset($value[$column]) ? $value[$column] : null;
+ }
+ if ($value === null) {
+ $parts[] = "$columnAlias==nil";
+ } elseif ($value instanceof Expression) {
+ $parts[] = "$columnAlias==" . $value->expression;
+ } else {
+ $value = $this->quoteValue($value);
+ $parts[] = "$columnAlias==$value";
+ }
+ }
+ $operator = $operator === 'in' ? '' : 'not ';
+
+ return "$operator(" . implode(' or ', $parts) . ')';
+ }
+
+ protected function buildCompositeInCondition($operator, $inColumns, $values, &$columns)
+ {
+ $vss = [];
+ foreach ($values as $value) {
+ $vs = [];
+ foreach ($inColumns as $column) {
+ $column = $this->addColumn($column, $columns);
+ if (isset($value[$column])) {
+ $vs[] = "$column==" . $this->quoteValue($value[$column]);
+ } else {
+ $vs[] = "$column==nil";
+ }
+ }
+ $vss[] = '(' . implode(' and ', $vs) . ')';
+ }
+ $operator = $operator === 'in' ? '' : 'not ';
+
+ return "$operator(" . implode(' or ', $vss) . ')';
+ }
+
+ private function buildLikeCondition($operator, $operands, &$columns)
+ {
+ throw new NotSupportedException('LIKE conditions are not suppoerted by redis ActiveRecord.');
+ }
}
diff --git a/extensions/redis/Session.php b/extensions/redis/Session.php
index a9c3bdd29cc..1d1fe7608a8 100644
--- a/extensions/redis/Session.php
+++ b/extensions/redis/Session.php
@@ -55,99 +55,100 @@
*/
class Session extends \yii\web\Session
{
- /**
- * @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
- * This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
- * redis connection as an application component.
- * After the Session object is created, if you want to change this property, you should only assign it
- * with a Redis [[Connection]] object.
- */
- public $redis = 'redis';
- /**
- * @var string a string prefixed to every cache key so that it is unique. If not set,
- * it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
- * if you don't want to use key prefix. It is recommended that you explicitly set this property to some
- * static value if the cached data needs to be shared among multiple applications.
- */
- public $keyPrefix;
+ /**
+ * @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
+ * This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
+ * redis connection as an application component.
+ * After the Session object is created, if you want to change this property, you should only assign it
+ * with a Redis [[Connection]] object.
+ */
+ public $redis = 'redis';
+ /**
+ * @var string a string prefixed to every cache key so that it is unique. If not set,
+ * it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
+ * if you don't want to use key prefix. It is recommended that you explicitly set this property to some
+ * static value if the cached data needs to be shared among multiple applications.
+ */
+ public $keyPrefix;
- /**
- * Initializes the redis Session component.
- * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
- * @throws InvalidConfigException if [[redis]] is invalid.
- */
- public function init()
- {
- if (is_string($this->redis)) {
- $this->redis = Yii::$app->getComponent($this->redis);
- } elseif (is_array($this->redis)) {
- if (!isset($this->redis['class'])) {
- $this->redis['class'] = Connection::className();
- }
- $this->redis = Yii::createObject($this->redis);
- }
- if (!$this->redis instanceof Connection) {
- throw new InvalidConfigException("Session::redis must be either a Redis connection instance or the application component ID of a Redis connection.");
- }
- if ($this->keyPrefix === null) {
- $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
- }
- parent::init();
- }
+ /**
+ * Initializes the redis Session component.
+ * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
+ * @throws InvalidConfigException if [[redis]] is invalid.
+ */
+ public function init()
+ {
+ if (is_string($this->redis)) {
+ $this->redis = Yii::$app->getComponent($this->redis);
+ } elseif (is_array($this->redis)) {
+ if (!isset($this->redis['class'])) {
+ $this->redis['class'] = Connection::className();
+ }
+ $this->redis = Yii::createObject($this->redis);
+ }
+ if (!$this->redis instanceof Connection) {
+ throw new InvalidConfigException("Session::redis must be either a Redis connection instance or the application component ID of a Redis connection.");
+ }
+ if ($this->keyPrefix === null) {
+ $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
+ }
+ parent::init();
+ }
- /**
- * Returns a value indicating whether to use custom session storage.
- * This method overrides the parent implementation and always returns true.
- * @return boolean whether to use custom storage.
- */
- public function getUseCustomStorage()
- {
- return true;
- }
+ /**
+ * Returns a value indicating whether to use custom session storage.
+ * This method overrides the parent implementation and always returns true.
+ * @return boolean whether to use custom storage.
+ */
+ public function getUseCustomStorage()
+ {
+ return true;
+ }
- /**
- * Session read handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return string the session data
- */
- public function readSession($id)
- {
- $data = $this->redis->executeCommand('GET', [$this->calculateKey($id)]);
- return $data === false ? '' : $data;
- }
+ /**
+ * Session read handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return string the session data
+ */
+ public function readSession($id)
+ {
+ $data = $this->redis->executeCommand('GET', [$this->calculateKey($id)]);
- /**
- * Session write handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @param string $data session data
- * @return boolean whether session write is successful
- */
- public function writeSession($id, $data)
- {
- return (bool) $this->redis->executeCommand('SET', [$this->calculateKey($id), $data, 'EX', $this->getTimeout()]);
- }
+ return $data === false ? '' : $data;
+ }
- /**
- * Session destroy handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return boolean whether session is destroyed successfully
- */
- public function destroySession($id)
- {
- return (bool) $this->redis->executeCommand('DEL', [$this->calculateKey($id)]);
- }
+ /**
+ * Session write handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @param string $data session data
+ * @return boolean whether session write is successful
+ */
+ public function writeSession($id, $data)
+ {
+ return (bool) $this->redis->executeCommand('SET', [$this->calculateKey($id), $data, 'EX', $this->getTimeout()]);
+ }
- /**
- * Generates a unique key used for storing session data in cache.
- * @param string $id session variable name
- * @return string a safe cache key associated with the session variable name
- */
- protected function calculateKey($id)
- {
- return $this->keyPrefix . md5(json_encode([__CLASS__, $id]));
- }
+ /**
+ * Session destroy handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return boolean whether session is destroyed successfully
+ */
+ public function destroySession($id)
+ {
+ return (bool) $this->redis->executeCommand('DEL', [$this->calculateKey($id)]);
+ }
+
+ /**
+ * Generates a unique key used for storing session data in cache.
+ * @param string $id session variable name
+ * @return string a safe cache key associated with the session variable name
+ */
+ protected function calculateKey($id)
+ {
+ return $this->keyPrefix . md5(json_encode([__CLASS__, $id]));
+ }
}
diff --git a/extensions/smarty/ViewRenderer.php b/extensions/smarty/ViewRenderer.php
index 574020a93a8..0efec276ab8 100644
--- a/extensions/smarty/ViewRenderer.php
+++ b/extensions/smarty/ViewRenderer.php
@@ -23,76 +23,76 @@
*/
class ViewRenderer extends BaseViewRenderer
{
- /**
- * @var string the directory or path alias pointing to where Smarty cache will be stored.
- */
- public $cachePath = '@runtime/Smarty/cache';
+ /**
+ * @var string the directory or path alias pointing to where Smarty cache will be stored.
+ */
+ public $cachePath = '@runtime/Smarty/cache';
- /**
- * @var string the directory or path alias pointing to where Smarty compiled templates will be stored.
- */
- public $compilePath = '@runtime/Smarty/compile';
+ /**
+ * @var string the directory or path alias pointing to where Smarty compiled templates will be stored.
+ */
+ public $compilePath = '@runtime/Smarty/compile';
- /**
- * @var Smarty
- */
- public $smarty;
+ /**
+ * @var Smarty
+ */
+ public $smarty;
- public function init()
- {
- $this->smarty = new Smarty();
- $this->smarty->setCompileDir(Yii::getAlias($this->compilePath));
- $this->smarty->setCacheDir(Yii::getAlias($this->cachePath));
+ public function init()
+ {
+ $this->smarty = new Smarty();
+ $this->smarty->setCompileDir(Yii::getAlias($this->compilePath));
+ $this->smarty->setCacheDir(Yii::getAlias($this->cachePath));
- $this->smarty->registerPlugin('function', 'path', [$this, 'smarty_function_path']);
- }
+ $this->smarty->registerPlugin('function', 'path', [$this, 'smarty_function_path']);
+ }
- /**
- * Smarty template function to get a path for using in links
- *
- * Usage is the following:
- *
- * {path route='blog/view' alias=$post.alias user=$user.id}
- *
- * where route is Yii route and the rest of parameters are passed as is.
- *
- * @param $params
- * @param \Smarty_Internal_Template $template
- *
- * @return string
- */
- public function smarty_function_path($params, \Smarty_Internal_Template $template)
- {
- if (!isset($params['route'])) {
- trigger_error("path: missing 'route' parameter");
- }
+ /**
+ * Smarty template function to get a path for using in links
+ *
+ * Usage is the following:
+ *
+ * {path route='blog/view' alias=$post.alias user=$user.id}
+ *
+ * where route is Yii route and the rest of parameters are passed as is.
+ *
+ * @param $params
+ * @param \Smarty_Internal_Template $template
+ *
+ * @return string
+ */
+ public function smarty_function_path($params, \Smarty_Internal_Template $template)
+ {
+ if (!isset($params['route'])) {
+ trigger_error("path: missing 'route' parameter");
+ }
- array_unshift($params, $params['route']) ;
- unset($params['route']);
+ array_unshift($params, $params['route']) ;
+ unset($params['route']);
- return Url::to($params);
- }
+ return Url::to($params);
+ }
- /**
- * Renders a view file.
- *
- * This method is invoked by [[View]] whenever it tries to render a view.
- * Child classes must implement this method to render the given view file.
- *
- * @param View $view the view object used for rendering the file.
- * @param string $file the view file.
- * @param array $params the parameters to be passed to the view file.
- *
- * @return string the rendering result
- */
- public function render($view, $file, $params)
- {
- /** @var \Smarty_Internal_Template $template */
- $template = $this->smarty->createTemplate($file, null, null, empty($params) ? null : $params, true);
+ /**
+ * Renders a view file.
+ *
+ * This method is invoked by [[View]] whenever it tries to render a view.
+ * Child classes must implement this method to render the given view file.
+ *
+ * @param View $view the view object used for rendering the file.
+ * @param string $file the view file.
+ * @param array $params the parameters to be passed to the view file.
+ *
+ * @return string the rendering result
+ */
+ public function render($view, $file, $params)
+ {
+ /** @var \Smarty_Internal_Template $template */
+ $template = $this->smarty->createTemplate($file, null, null, empty($params) ? null : $params, true);
- $template->assign('app', \Yii::$app);
- $template->assign('this', $view);
+ $template->assign('app', \Yii::$app);
+ $template->assign('this', $view);
- return $template->fetch();
- }
+ return $template->fetch();
+ }
}
diff --git a/extensions/sphinx/ActiveQuery.php b/extensions/sphinx/ActiveQuery.php
index 42391f97ae4..31e86c989be 100644
--- a/extensions/sphinx/ActiveQuery.php
+++ b/extensions/sphinx/ActiveQuery.php
@@ -82,186 +82,193 @@
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
- use ActiveQueryTrait;
- use ActiveRelationTrait;
+ use ActiveQueryTrait;
+ use ActiveRelationTrait;
- /**
- * @var string the SQL statement to be executed for retrieving AR records.
- * This is set by [[ActiveRecord::findBySql()]].
- */
- public $sql;
+ /**
+ * @var string the SQL statement to be executed for retrieving AR records.
+ * This is set by [[ActiveRecord::findBySql()]].
+ */
+ public $sql;
- /**
- * Sets the [[snippetCallback]] to [[fetchSnippetSourceFromModels()]], which allows to
- * fetch the snippet source strings from the Active Record models, using method
- * [[ActiveRecord::getSnippetSource()]].
- * For example:
- *
- * ~~~
- * class Article extends ActiveRecord
- * {
- * public function getSnippetSource()
- * {
- * return file_get_contents('/path/to/source/files/' . $this->id . '.txt');;
- * }
- * }
- *
- * $articles = Article::find()->snippetByModel()->all();
- * ~~~
- *
- * Warning: this option should NOT be used with [[asArray]] at the same time!
- * @return static the query object itself
- */
- public function snippetByModel()
- {
- $this->snippetCallback([$this, 'fetchSnippetSourceFromModels']);
- return $this;
- }
+ /**
+ * Sets the [[snippetCallback]] to [[fetchSnippetSourceFromModels()]], which allows to
+ * fetch the snippet source strings from the Active Record models, using method
+ * [[ActiveRecord::getSnippetSource()]].
+ * For example:
+ *
+ * ~~~
+ * class Article extends ActiveRecord
+ * {
+ * public function getSnippetSource()
+ * {
+ * return file_get_contents('/path/to/source/files/' . $this->id . '.txt');;
+ * }
+ * }
+ *
+ * $articles = Article::find()->snippetByModel()->all();
+ * ~~~
+ *
+ * Warning: this option should NOT be used with [[asArray]] at the same time!
+ * @return static the query object itself
+ */
+ public function snippetByModel()
+ {
+ $this->snippetCallback([$this, 'fetchSnippetSourceFromModels']);
- /**
- * Executes query and returns all results as an array.
- * @param Connection $db the DB connection used to create the DB command.
- * If null, the DB connection returned by [[modelClass]] will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- $command = $this->createCommand($db);
- $rows = $command->queryAll();
- if (!empty($rows)) {
- $models = $this->createModels($rows);
- if (!empty($this->with)) {
- $this->findWith($this->with, $models);
- }
- $models = $this->fillUpSnippets($models);
- if (!$this->asArray) {
- foreach ($models as $model) {
- $model->afterFind();
- }
- }
- return $models;
- } else {
- return [];
- }
- }
+ return $this;
+ }
- /**
- * Executes query and returns a single row of result.
- * @param Connection $db the DB connection used to create the DB command.
- * If null, the DB connection returned by [[modelClass]] will be used.
- * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
- * the query result may be either an array or an ActiveRecord object. Null will be returned
- * if the query results in nothing.
- */
- public function one($db = null)
- {
- $command = $this->createCommand($db);
- $row = $command->queryOne();
- if ($row !== false) {
- if ($this->asArray) {
- $model = $row;
- } else {
- /** @var $class ActiveRecord */
- $class = $this->modelClass;
- $model = $class::instantiate($row);
- $class::populateRecord($model, $row);
- }
- if (!empty($this->with)) {
- $models = [$model];
- $this->findWith($this->with, $models);
- $model = $models[0];
- }
- list ($model) = $this->fillUpSnippets([$model]);
- if (!$this->asArray) {
- $model->afterFind();
- }
- return $model;
- } else {
- return null;
- }
- }
+ /**
+ * Executes query and returns all results as an array.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $command = $this->createCommand($db);
+ $rows = $command->queryAll();
+ if (!empty($rows)) {
+ $models = $this->createModels($rows);
+ if (!empty($this->with)) {
+ $this->findWith($this->with, $models);
+ }
+ $models = $this->fillUpSnippets($models);
+ if (!$this->asArray) {
+ foreach ($models as $model) {
+ $model->afterFind();
+ }
+ }
- /**
- * Creates a DB command that can be used to execute this query.
- * @param Connection $db the DB connection used to create the DB command.
- * If null, the DB connection returned by [[modelClass]] will be used.
- * @return Command the created DB command instance.
- */
- public function createCommand($db = null)
- {
- if ($this->primaryModel !== null) {
- // lazy loading a relational query
- if ($this->via instanceof self) {
- // via pivot index
- $viaModels = $this->via->findPivotRows([$this->primaryModel]);
- $this->filterByModels($viaModels);
- } elseif (is_array($this->via)) {
- // via relation
- /** @var ActiveQuery $viaQuery */
- list($viaName, $viaQuery) = $this->via;
- if ($viaQuery->multiple) {
- $viaModels = $viaQuery->all();
- $this->primaryModel->populateRelation($viaName, $viaModels);
- } else {
- $model = $viaQuery->one();
- $this->primaryModel->populateRelation($viaName, $model);
- $viaModels = $model === null ? [] : [$model];
- }
- $this->filterByModels($viaModels);
- } else {
- $this->filterByModels([$this->primaryModel]);
- }
- }
+ return $models;
+ } else {
+ return [];
+ }
+ }
- $this->setConnection($db);
- $db = $this->getConnection();
+ /**
+ * Executes query and returns a single row of result.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
+ * the query result may be either an array or an ActiveRecord object. Null will be returned
+ * if the query results in nothing.
+ */
+ public function one($db = null)
+ {
+ $command = $this->createCommand($db);
+ $row = $command->queryOne();
+ if ($row !== false) {
+ if ($this->asArray) {
+ $model = $row;
+ } else {
+ /** @var $class ActiveRecord */
+ $class = $this->modelClass;
+ $model = $class::instantiate($row);
+ $class::populateRecord($model, $row);
+ }
+ if (!empty($this->with)) {
+ $models = [$model];
+ $this->findWith($this->with, $models);
+ $model = $models[0];
+ }
+ list ($model) = $this->fillUpSnippets([$model]);
+ if (!$this->asArray) {
+ $model->afterFind();
+ }
- $params = $this->params;
- if ($this->sql === null) {
- list ($this->sql, $params) = $db->getQueryBuilder()->build($this);
- }
- return $db->createCommand($this->sql, $params);
- }
+ return $model;
+ } else {
+ return null;
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function defaultConnection()
- {
- $modelClass = $this->modelClass;
- return $modelClass::getDb();
- }
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return Command the created DB command instance.
+ */
+ public function createCommand($db = null)
+ {
+ if ($this->primaryModel !== null) {
+ // lazy loading a relational query
+ if ($this->via instanceof self) {
+ // via pivot index
+ $viaModels = $this->via->findPivotRows([$this->primaryModel]);
+ $this->filterByModels($viaModels);
+ } elseif (is_array($this->via)) {
+ // via relation
+ /** @var ActiveQuery $viaQuery */
+ list($viaName, $viaQuery) = $this->via;
+ if ($viaQuery->multiple) {
+ $viaModels = $viaQuery->all();
+ $this->primaryModel->populateRelation($viaName, $viaModels);
+ } else {
+ $model = $viaQuery->one();
+ $this->primaryModel->populateRelation($viaName, $model);
+ $viaModels = $model === null ? [] : [$model];
+ }
+ $this->filterByModels($viaModels);
+ } else {
+ $this->filterByModels([$this->primaryModel]);
+ }
+ }
- /**
- * Fetches the source for the snippets using [[ActiveRecord::getSnippetSource()]] method.
- * @param ActiveRecord[] $models raw query result rows.
- * @throws \yii\base\InvalidCallException if [[asArray]] enabled.
- * @return array snippet source strings
- */
- protected function fetchSnippetSourceFromModels($models)
- {
- if ($this->asArray) {
- throw new InvalidCallException('"' . __METHOD__ . '" unable to determine snippet source from plain array. Either disable "asArray" option or use regular "snippetCallback"');
- }
- $result = [];
- foreach ($models as $model) {
- $result[] = $model->getSnippetSource();
- }
- return $result;
- }
+ $this->setConnection($db);
+ $db = $this->getConnection();
- /**
- * @inheritdoc
- */
- protected function callSnippets(array $source)
- {
- $from = $this->from;
- if ($from === null) {
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- $tableName = $modelClass::indexName();
- $from = [$tableName];
- }
- return $this->callSnippetsInternal($source, $from[0]);
- }
+ $params = $this->params;
+ if ($this->sql === null) {
+ list ($this->sql, $params) = $db->getQueryBuilder()->build($this);
+ }
+
+ return $db->createCommand($this->sql, $params);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function defaultConnection()
+ {
+ $modelClass = $this->modelClass;
+
+ return $modelClass::getDb();
+ }
+
+ /**
+ * Fetches the source for the snippets using [[ActiveRecord::getSnippetSource()]] method.
+ * @param ActiveRecord[] $models raw query result rows.
+ * @throws \yii\base\InvalidCallException if [[asArray]] enabled.
+ * @return array snippet source strings
+ */
+ protected function fetchSnippetSourceFromModels($models)
+ {
+ if ($this->asArray) {
+ throw new InvalidCallException('"' . __METHOD__ . '" unable to determine snippet source from plain array. Either disable "asArray" option or use regular "snippetCallback"');
+ }
+ $result = [];
+ foreach ($models as $model) {
+ $result[] = $model->getSnippetSource();
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function callSnippets(array $source)
+ {
+ $from = $this->from;
+ if ($from === null) {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+ $tableName = $modelClass::indexName();
+ $from = [$tableName];
+ }
+
+ return $this->callSnippetsInternal($source, $from[0]);
+ }
}
diff --git a/extensions/sphinx/ActiveRecord.php b/extensions/sphinx/ActiveRecord.php
index cbfe9e1e629..e25ab70f34e 100644
--- a/extensions/sphinx/ActiveRecord.php
+++ b/extensions/sphinx/ActiveRecord.php
@@ -29,622 +29,636 @@
*/
abstract class ActiveRecord extends BaseActiveRecord
{
- /**
- * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
- */
- const OP_INSERT = 0x01;
- /**
- * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
- */
- const OP_UPDATE = 0x02;
- /**
- * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
- */
- const OP_DELETE = 0x04;
- /**
- * All three operations: insert, update, delete.
- * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
- */
- const OP_ALL = 0x07;
-
- /**
- * @var string current snippet value for this Active Record instance.
- * It will be filled up automatically when instance found using [[Query::snippetCallback]]
- * or [[ActiveQuery::snippetByModel()]].
- */
- private $_snippet;
-
- /**
- * Returns the Sphinx connection used by this AR class.
- * By default, the "sphinx" application component is used as the Sphinx connection.
- * You may override this method if you want to use a different Sphinx connection.
- * @return Connection the Sphinx connection used by this AR class.
- */
- public static function getDb()
- {
- return \Yii::$app->getComponent('sphinx');
- }
-
- /**
- * Creates an [[ActiveQuery]] instance with a given SQL statement.
- *
- * Note that because the SQL statement is already specified, calling additional
- * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]]
- * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is
- * still fine.
- *
- * Below is an example:
- *
- * ~~~
- * $customers = Article::findBySql("SELECT * FROM `idx_article` WHERE MATCH('development')")->all();
- * ~~~
- *
- * @param string $sql the SQL statement to be executed
- * @param array $params parameters to be bound to the SQL statement during execution.
- * @return ActiveQuery the newly created [[ActiveQuery]] instance
- */
- public static function findBySql($sql, $params = [])
- {
- $query = static::createQuery();
- $query->sql = $sql;
- return $query->params($params);
- }
-
- /**
- * Updates the whole table using the provided attribute values and conditions.
- * For example, to change the status to be 1 for all articles which status is 2:
- *
- * ~~~
- * Article::updateAll(['status' => 1], 'status = 2');
- * ~~~
- *
- * @param array $attributes attribute values (name-value pairs) to be saved into the table
- * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return integer the number of rows updated
- */
- public static function updateAll($attributes, $condition = '', $params = [])
- {
- $command = static::getDb()->createCommand();
- $command->update(static::indexName(), $attributes, $condition, $params);
- return $command->execute();
- }
-
- /**
- * Deletes rows in the index using the provided conditions.
- *
- * For example, to delete all articles whose status is 3:
- *
- * ~~~
- * Article::deleteAll('status = 3');
- * ~~~
- *
- * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return integer the number of rows deleted
- */
- public static function deleteAll($condition = '', $params = [])
- {
- $command = static::getDb()->createCommand();
- $command->delete(static::indexName(), $condition, $params);
- return $command->execute();
- }
-
- /**
- * Creates an [[ActiveQuery]] instance.
- *
- * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
- * by [[hasOne()]] and [[hasMany()]] to create a relational query.
- * You may override this method to return a customized query (e.g. `CustomerQuery` specified
- * written for querying `Customer` purpose.)
- *
- * You may also define default conditions that should apply to all queries unless overridden:
- *
- * ```php
- * public static function createQuery($config = [])
- * {
- * return parent::createQuery($config)->where(['deleted' => false]);
- * }
- * ```
- *
- * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
- * default condition. Using [[Query::where()]] will override the default condition.
- *
- * @param array $config the configuration passed to the ActiveQuery class.
- * @return ActiveQuery the newly created [[ActiveQuery]] instance.
- */
- public static function createQuery($config = [])
- {
- $config['modelClass'] = get_called_class();
- return new ActiveQuery($config);
- }
-
- /**
- * Declares the name of the Sphinx index associated with this AR class.
- * By default this method returns the class name as the index name by calling [[Inflector::camel2id()]].
- * For example, 'Article' becomes 'article', and 'StockItem' becomes
- * 'stock_item'. You may override this method if the index is not named after this convention.
- * @return string the index name
- */
- public static function indexName()
- {
- return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
- }
-
- /**
- * Returns the schema information of the Sphinx index associated with this AR class.
- * @return IndexSchema the schema information of the Sphinx index associated with this AR class.
- * @throws InvalidConfigException if the index for the AR class does not exist.
- */
- public static function getIndexSchema()
- {
- $schema = static::getDb()->getIndexSchema(static::indexName());
- if ($schema !== null) {
- return $schema;
- } else {
- throw new InvalidConfigException("The index does not exist: " . static::indexName());
- }
- }
-
- /**
- * Returns the primary key name for this AR class.
- * The default implementation will return the primary key as declared
- * in the Sphinx index, which is associated with this AR class.
- *
- * Note that an array should be returned even for a table with single primary key.
- *
- * @return string[] the primary keys of the associated Sphinx index.
- */
- public static function primaryKey()
- {
- return [static::getIndexSchema()->primaryKey];
- }
-
- /**
- * Builds a snippet from provided data and query, using specified index settings.
- * @param string|array $source is the source data to extract a snippet from.
- * It could be either a single string or array of strings.
- * @param string $match the full-text query to build snippets for.
- * @param array $options list of options in format: optionName => optionValue
- * @return string|array built snippet in case "source" is a string, list of built snippets
- * in case "source" is an array.
- */
- public static function callSnippets($source, $match, $options = [])
- {
- $command = static::getDb()->createCommand();
- $command->callSnippets(static::indexName(), $source, $match, $options);
- if (is_array($source)) {
- return $command->queryColumn();
- } else {
- return $command->queryScalar();
- }
- }
-
- /**
- * Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics.
- * @param string $text the text to break down to keywords.
- * @param boolean $fetchStatistic whether to return document and hit occurrence statistics
- * @return array keywords and statistics
- */
- public static function callKeywords($text, $fetchStatistic = false)
- {
- $command = static::getDb()->createCommand();
- $command->callKeywords(static::indexName(), $text, $fetchStatistic);
- return $command->queryAll();
- }
-
- /**
- * @param string $snippet
- */
- public function setSnippet($snippet)
- {
- $this->_snippet = $snippet;
- }
-
- /**
- * Returns current snippet value or generates new one from given match.
- * @param string $match snippet source query
- * @param array $options list of options in format: optionName => optionValue
- * @return string snippet value
- */
- public function getSnippet($match = null, $options = [])
- {
- if ($match !== null) {
- $this->_snippet = $this->fetchSnippet($match, $options);
- }
- return $this->_snippet;
- }
-
- /**
- * Builds up the snippet value from the given query.
- * @param string $match the full-text query to build snippets for.
- * @param array $options list of options in format: optionName => optionValue
- * @return string snippet value.
- */
- protected function fetchSnippet($match, $options = [])
- {
- return static::callSnippets($this->getSnippetSource(), $match, $options);
- }
-
- /**
- * Returns the string, which should be used as a source to create snippet for this
- * Active Record instance.
- * Child classes must implement this method to return the actual snippet source text.
- * For example:
- * ~~~
- * public function getSnippetSource()
- * {
- * return $this->snippetSourceRelation->content;
- * }
- * ~~~
- * @return string snippet source string.
- * @throws \yii\base\NotSupportedException if this is not supported by the Active Record class
- */
- public function getSnippetSource()
- {
- throw new NotSupportedException($this->className() . ' does not provide snippet source.');
- }
-
- /**
- * Declares which operations should be performed within a transaction in different scenarios.
- * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
- * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
- * By default, these methods are NOT enclosed in a transaction.
- *
- * In some scenarios, to ensure data consistency, you may want to enclose some or all of them
- * in transactions. You can do so by overriding this method and returning the operations
- * that need to be transactional. For example,
- *
- * ~~~
- * return [
- * 'admin' => self::OP_INSERT,
- * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
- * // the above is equivalent to the following:
- * // 'api' => self::OP_ALL,
- *
- * ];
- * ~~~
- *
- * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
- * should be done in a transaction; and in the "api" scenario, all the operations should be done
- * in a transaction.
- *
- * @return array the declarations of transactional operations. The array keys are scenarios names,
- * and the array values are the corresponding transaction operations.
- */
- public function transactions()
- {
- return [];
- }
-
- /**
- * Returns the list of all attribute names of the model.
- * The default implementation will return all column names of the table associated with this AR class.
- * @return array list of attribute names.
- */
- public function attributes()
- {
- return array_keys(static::getIndexSchema()->columns);
- }
-
- /**
- * Inserts a row into the associated Sphinx index using the attribute values of this record.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
- * fails, it will skip the rest of the steps;
- * 2. call [[afterValidate()]] when `$runValidation` is true.
- * 3. call [[beforeSave()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 4. insert the record into index. If this fails, it will skip the rest of the steps;
- * 5. call [[afterSave()]];
- *
- * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
- * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
- * will be raised by the corresponding methods.
- *
- * Only the [[changedAttributes|changed attribute values]] will be inserted.
- *
- * For example, to insert an article record:
- *
- * ~~~
- * $article = new Article;
- * $article->id = $id;
- * $article->genre_id = $genreId;
- * $article->content = $content;
- * $article->insert();
- * ~~~
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from index will be saved.
- * @return boolean whether the attributes are valid and the record is inserted successfully.
- * @throws \Exception in case insert failed.
- */
- public function insert($runValidation = true, $attributes = null)
- {
- if ($runValidation && !$this->validate($attributes)) {
- return false;
- }
- $db = static::getDb();
- if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) {
- $transaction = $db->beginTransaction();
- try {
- $result = $this->insertInternal($attributes);
- if ($result === false) {
- $transaction->rollBack();
- } else {
- $transaction->commit();
- }
- } catch (\Exception $e) {
- $transaction->rollBack();
- throw $e;
- }
- } else {
- $result = $this->insertInternal($attributes);
- }
- return $result;
- }
-
- /**
- * @see ActiveRecord::insert()
- */
- private function insertInternal($attributes = null)
- {
- if (!$this->beforeSave(true)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- foreach ($this->getPrimaryKey(true) as $key => $value) {
- $values[$key] = $value;
- }
- }
- $db = static::getDb();
- $command = $db->createCommand()->insert($this->indexName(), $values);
- if (!$command->execute()) {
- return false;
- }
- foreach ($values as $name => $value) {
- $this->setOldAttribute($name, $value);
- }
- $this->afterSave(true);
- return true;
- }
-
- /**
- * Saves the changes to this active record into the associated Sphinx index.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
- * fails, it will skip the rest of the steps;
- * 2. call [[afterValidate()]] when `$runValidation` is true.
- * 3. call [[beforeSave()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 4. save the record into index. If this fails, it will skip the rest of the steps;
- * 5. call [[afterSave()]];
- *
- * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
- * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
- * will be raised by the corresponding methods.
- *
- * Only the [[changedAttributes|changed attribute values]] will be saved into database.
- *
- * For example, to update an article record:
- *
- * ~~~
- * $article = Article::find(['id' => $id]);
- * $article->genre_id = $genreId;
- * $article->group_id = $groupId;
- * $article->update();
- * ~~~
- *
- * Note that it is possible the update does not affect any row in the table.
- * In this case, this method will return 0. For this reason, you should use the following
- * code to check if update() is successful or not:
- *
- * ~~~
- * if ($this->update() !== false) {
- * // update successful
- * } else {
- * // update failed
- * }
- * ~~~
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted into the database.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return integer|boolean the number of rows affected, or false if validation fails
- * or [[beforeSave()]] stops the updating process.
- * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
- * being updated is outdated.
- * @throws \Exception in case update failed.
- */
- public function update($runValidation = true, $attributes = null)
- {
- if ($runValidation && !$this->validate($attributes)) {
- return false;
- }
- $db = static::getDb();
- if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) {
- $transaction = $db->beginTransaction();
- try {
- $result = $this->updateInternal($attributes);
- if ($result === false) {
- $transaction->rollBack();
- } else {
- $transaction->commit();
- }
- } catch (\Exception $e) {
- $transaction->rollBack();
- throw $e;
- }
- } else {
- $result = $this->updateInternal($attributes);
- }
- return $result;
- }
-
- /**
- * @see CActiveRecord::update()
- * @throws StaleObjectException
- */
- protected function updateInternal($attributes = null)
- {
- if (!$this->beforeSave(false)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- $this->afterSave(false);
- return 0;
- }
-
- // Replace is supported only by runtime indexes and necessary only for field update
- $useReplace = false;
- $indexSchema = $this->getIndexSchema();
- if ($this->getIndexSchema()->isRuntime) {
- foreach ($values as $name => $value) {
- $columnSchema = $indexSchema->getColumn($name);
- if ($columnSchema->isField) {
- $useReplace = true;
- break;
- }
- }
- }
-
- if ($useReplace) {
- $values = array_merge($values, $this->getOldPrimaryKey(true));
- $command = static::getDb()->createCommand();
- $command->replace(static::indexName(), $values);
- // We do not check the return value of replace because it's possible
- // that the REPLACE statement doesn't change anything and thus returns 0.
- $rows = $command->execute();
- } else {
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- if (!isset($values[$lock])) {
- $values[$lock] = $this->$lock + 1;
- }
- $condition[$lock] = $this->$lock;
- }
- // We do not check the return value of updateAll() because it's possible
- // that the UPDATE statement doesn't change anything and thus returns 0.
- $rows = $this->updateAll($values, $condition);
-
- if ($lock !== null && !$rows) {
- throw new StaleObjectException('The object being updated is outdated.');
- }
- }
-
- foreach ($values as $name => $value) {
- $this->setOldAttribute($name, $this->getAttribute($name));
- }
- $this->afterSave(false);
- return $rows;
- }
-
- /**
- * Deletes the index entry corresponding to this active record.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 2. delete the record from the index;
- * 3. call [[afterDelete()]].
- *
- * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
- * will be raised by the corresponding methods.
- *
- * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
- * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
- * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
- * being deleted is outdated.
- * @throws \Exception in case delete failed.
- */
- public function delete()
- {
- $db = static::getDb();
- $transaction = $this->isTransactional(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
- try {
- $result = false;
- if ($this->beforeDelete()) {
- // we do not check the return value of deleteAll() because it's possible
- // the record is already deleted in the database and thus the method will return 0
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- $condition[$lock] = $this->$lock;
- }
- $result = $this->deleteAll($condition);
- if ($lock !== null && !$result) {
- throw new StaleObjectException('The object being deleted is outdated.');
- }
- $this->setOldAttributes(null);
- $this->afterDelete();
- }
- if ($transaction !== null) {
- if ($result === false) {
- $transaction->rollBack();
- } else {
- $transaction->commit();
- }
- }
- } catch (\Exception $e) {
- if ($transaction !== null) {
- $transaction->rollBack();
- }
- throw $e;
- }
- return $result;
- }
-
- /**
- * Returns a value indicating whether the given active record is the same as the current one.
- * The comparison is made by comparing the index names and the primary key values of the two active records.
- * If one of the records [[isNewRecord|is new]] they are also considered not equal.
- * @param ActiveRecord $record record to compare to
- * @return boolean whether the two active records refer to the same row in the same index.
- */
- public function equals($record)
- {
- if ($this->isNewRecord || $record->isNewRecord) {
- return false;
- }
- return $this->indexName() === $record->indexName() && $this->getPrimaryKey() === $record->getPrimaryKey();
- }
-
- /**
- * @inheritdoc
- */
- public static function populateRecord($record, $row)
- {
- $columns = static::getIndexSchema()->columns;
- foreach ($row as $name => $value) {
- if (isset($columns[$name]) && $columns[$name]->isMva) {
- $row[$name] = explode(',', $value);
- }
- }
- parent::populateRecord($record, $row);
- }
-
- /**
- * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
- * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
- * @return boolean whether the specified operation is transactional in the current [[scenario]].
- */
- public function isTransactional($operation)
- {
- $scenario = $this->getScenario();
- $transactions = $this->transactions();
- return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
- }
+ /**
+ * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_INSERT = 0x01;
+ /**
+ * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_UPDATE = 0x02;
+ /**
+ * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_DELETE = 0x04;
+ /**
+ * All three operations: insert, update, delete.
+ * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
+ */
+ const OP_ALL = 0x07;
+
+ /**
+ * @var string current snippet value for this Active Record instance.
+ * It will be filled up automatically when instance found using [[Query::snippetCallback]]
+ * or [[ActiveQuery::snippetByModel()]].
+ */
+ private $_snippet;
+
+ /**
+ * Returns the Sphinx connection used by this AR class.
+ * By default, the "sphinx" application component is used as the Sphinx connection.
+ * You may override this method if you want to use a different Sphinx connection.
+ * @return Connection the Sphinx connection used by this AR class.
+ */
+ public static function getDb()
+ {
+ return \Yii::$app->getComponent('sphinx');
+ }
+
+ /**
+ * Creates an [[ActiveQuery]] instance with a given SQL statement.
+ *
+ * Note that because the SQL statement is already specified, calling additional
+ * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]]
+ * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is
+ * still fine.
+ *
+ * Below is an example:
+ *
+ * ~~~
+ * $customers = Article::findBySql("SELECT * FROM `idx_article` WHERE MATCH('development')")->all();
+ * ~~~
+ *
+ * @param string $sql the SQL statement to be executed
+ * @param array $params parameters to be bound to the SQL statement during execution.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance
+ */
+ public static function findBySql($sql, $params = [])
+ {
+ $query = static::createQuery();
+ $query->sql = $sql;
+
+ return $query->params($params);
+ }
+
+ /**
+ * Updates the whole table using the provided attribute values and conditions.
+ * For example, to change the status to be 1 for all articles which status is 2:
+ *
+ * ~~~
+ * Article::updateAll(['status' => 1], 'status = 2');
+ * ~~~
+ *
+ * @param array $attributes attribute values (name-value pairs) to be saved into the table
+ * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return integer the number of rows updated
+ */
+ public static function updateAll($attributes, $condition = '', $params = [])
+ {
+ $command = static::getDb()->createCommand();
+ $command->update(static::indexName(), $attributes, $condition, $params);
+
+ return $command->execute();
+ }
+
+ /**
+ * Deletes rows in the index using the provided conditions.
+ *
+ * For example, to delete all articles whose status is 3:
+ *
+ * ~~~
+ * Article::deleteAll('status = 3');
+ * ~~~
+ *
+ * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return integer the number of rows deleted
+ */
+ public static function deleteAll($condition = '', $params = [])
+ {
+ $command = static::getDb()->createCommand();
+ $command->delete(static::indexName(), $condition, $params);
+
+ return $command->execute();
+ }
+
+ /**
+ * Creates an [[ActiveQuery]] instance.
+ *
+ * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
+ * by [[hasOne()]] and [[hasMany()]] to create a relational query.
+ * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+ * written for querying `Customer` purpose.)
+ *
+ * You may also define default conditions that should apply to all queries unless overridden:
+ *
+ * ```php
+ * public static function createQuery($config = [])
+ * {
+ * return parent::createQuery($config)->where(['deleted' => false]);
+ * }
+ * ```
+ *
+ * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
+ * default condition. Using [[Query::where()]] will override the default condition.
+ *
+ * @param array $config the configuration passed to the ActiveQuery class.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance.
+ */
+ public static function createQuery($config = [])
+ {
+ $config['modelClass'] = get_called_class();
+
+ return new ActiveQuery($config);
+ }
+
+ /**
+ * Declares the name of the Sphinx index associated with this AR class.
+ * By default this method returns the class name as the index name by calling [[Inflector::camel2id()]].
+ * For example, 'Article' becomes 'article', and 'StockItem' becomes
+ * 'stock_item'. You may override this method if the index is not named after this convention.
+ * @return string the index name
+ */
+ public static function indexName()
+ {
+ return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
+ }
+
+ /**
+ * Returns the schema information of the Sphinx index associated with this AR class.
+ * @return IndexSchema the schema information of the Sphinx index associated with this AR class.
+ * @throws InvalidConfigException if the index for the AR class does not exist.
+ */
+ public static function getIndexSchema()
+ {
+ $schema = static::getDb()->getIndexSchema(static::indexName());
+ if ($schema !== null) {
+ return $schema;
+ } else {
+ throw new InvalidConfigException("The index does not exist: " . static::indexName());
+ }
+ }
+
+ /**
+ * Returns the primary key name for this AR class.
+ * The default implementation will return the primary key as declared
+ * in the Sphinx index, which is associated with this AR class.
+ *
+ * Note that an array should be returned even for a table with single primary key.
+ *
+ * @return string[] the primary keys of the associated Sphinx index.
+ */
+ public static function primaryKey()
+ {
+ return [static::getIndexSchema()->primaryKey];
+ }
+
+ /**
+ * Builds a snippet from provided data and query, using specified index settings.
+ * @param string|array $source is the source data to extract a snippet from.
+ * It could be either a single string or array of strings.
+ * @param string $match the full-text query to build snippets for.
+ * @param array $options list of options in format: optionName => optionValue
+ * @return string|array built snippet in case "source" is a string, list of built snippets
+ * in case "source" is an array.
+ */
+ public static function callSnippets($source, $match, $options = [])
+ {
+ $command = static::getDb()->createCommand();
+ $command->callSnippets(static::indexName(), $source, $match, $options);
+ if (is_array($source)) {
+ return $command->queryColumn();
+ } else {
+ return $command->queryScalar();
+ }
+ }
+
+ /**
+ * Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics.
+ * @param string $text the text to break down to keywords.
+ * @param boolean $fetchStatistic whether to return document and hit occurrence statistics
+ * @return array keywords and statistics
+ */
+ public static function callKeywords($text, $fetchStatistic = false)
+ {
+ $command = static::getDb()->createCommand();
+ $command->callKeywords(static::indexName(), $text, $fetchStatistic);
+
+ return $command->queryAll();
+ }
+
+ /**
+ * @param string $snippet
+ */
+ public function setSnippet($snippet)
+ {
+ $this->_snippet = $snippet;
+ }
+
+ /**
+ * Returns current snippet value or generates new one from given match.
+ * @param string $match snippet source query
+ * @param array $options list of options in format: optionName => optionValue
+ * @return string snippet value
+ */
+ public function getSnippet($match = null, $options = [])
+ {
+ if ($match !== null) {
+ $this->_snippet = $this->fetchSnippet($match, $options);
+ }
+
+ return $this->_snippet;
+ }
+
+ /**
+ * Builds up the snippet value from the given query.
+ * @param string $match the full-text query to build snippets for.
+ * @param array $options list of options in format: optionName => optionValue
+ * @return string snippet value.
+ */
+ protected function fetchSnippet($match, $options = [])
+ {
+ return static::callSnippets($this->getSnippetSource(), $match, $options);
+ }
+
+ /**
+ * Returns the string, which should be used as a source to create snippet for this
+ * Active Record instance.
+ * Child classes must implement this method to return the actual snippet source text.
+ * For example:
+ * ~~~
+ * public function getSnippetSource()
+ * {
+ * return $this->snippetSourceRelation->content;
+ * }
+ * ~~~
+ * @return string snippet source string.
+ * @throws \yii\base\NotSupportedException if this is not supported by the Active Record class
+ */
+ public function getSnippetSource()
+ {
+ throw new NotSupportedException($this->className() . ' does not provide snippet source.');
+ }
+
+ /**
+ * Declares which operations should be performed within a transaction in different scenarios.
+ * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
+ * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
+ * By default, these methods are NOT enclosed in a transaction.
+ *
+ * In some scenarios, to ensure data consistency, you may want to enclose some or all of them
+ * in transactions. You can do so by overriding this method and returning the operations
+ * that need to be transactional. For example,
+ *
+ * ~~~
+ * return [
+ * 'admin' => self::OP_INSERT,
+ * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
+ * // the above is equivalent to the following:
+ * // 'api' => self::OP_ALL,
+ *
+ * ];
+ * ~~~
+ *
+ * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
+ * should be done in a transaction; and in the "api" scenario, all the operations should be done
+ * in a transaction.
+ *
+ * @return array the declarations of transactional operations. The array keys are scenarios names,
+ * and the array values are the corresponding transaction operations.
+ */
+ public function transactions()
+ {
+ return [];
+ }
+
+ /**
+ * Returns the list of all attribute names of the model.
+ * The default implementation will return all column names of the table associated with this AR class.
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ return array_keys(static::getIndexSchema()->columns);
+ }
+
+ /**
+ * Inserts a row into the associated Sphinx index using the attribute values of this record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. insert the record into index. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[changedAttributes|changed attribute values]] will be inserted.
+ *
+ * For example, to insert an article record:
+ *
+ * ~~~
+ * $article = new Article;
+ * $article->id = $id;
+ * $article->genre_id = $genreId;
+ * $article->content = $content;
+ * $article->insert();
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from index will be saved.
+ * @return boolean whether the attributes are valid and the record is inserted successfully.
+ * @throws \Exception in case insert failed.
+ */
+ public function insert($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ $db = static::getDb();
+ if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) {
+ $transaction = $db->beginTransaction();
+ try {
+ $result = $this->insertInternal($attributes);
+ if ($result === false) {
+ $transaction->rollBack();
+ } else {
+ $transaction->commit();
+ }
+ } catch (\Exception $e) {
+ $transaction->rollBack();
+ throw $e;
+ }
+ } else {
+ $result = $this->insertInternal($attributes);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @see ActiveRecord::insert()
+ */
+ private function insertInternal($attributes = null)
+ {
+ if (!$this->beforeSave(true)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ foreach ($this->getPrimaryKey(true) as $key => $value) {
+ $values[$key] = $value;
+ }
+ }
+ $db = static::getDb();
+ $command = $db->createCommand()->insert($this->indexName(), $values);
+ if (!$command->execute()) {
+ return false;
+ }
+ foreach ($values as $name => $value) {
+ $this->setOldAttribute($name, $value);
+ }
+ $this->afterSave(true);
+
+ return true;
+ }
+
+ /**
+ * Saves the changes to this active record into the associated Sphinx index.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. save the record into index. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[changedAttributes|changed attribute values]] will be saved into database.
+ *
+ * For example, to update an article record:
+ *
+ * ~~~
+ * $article = Article::find(['id' => $id]);
+ * $article->genre_id = $genreId;
+ * $article->group_id = $groupId;
+ * $article->update();
+ * ~~~
+ *
+ * Note that it is possible the update does not affect any row in the table.
+ * In this case, this method will return 0. For this reason, you should use the following
+ * code to check if update() is successful or not:
+ *
+ * ~~~
+ * if ($this->update() !== false) {
+ * // update successful
+ * } else {
+ * // update failed
+ * }
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return integer|boolean the number of rows affected, or false if validation fails
+ * or [[beforeSave()]] stops the updating process.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being updated is outdated.
+ * @throws \Exception in case update failed.
+ */
+ public function update($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ $db = static::getDb();
+ if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) {
+ $transaction = $db->beginTransaction();
+ try {
+ $result = $this->updateInternal($attributes);
+ if ($result === false) {
+ $transaction->rollBack();
+ } else {
+ $transaction->commit();
+ }
+ } catch (\Exception $e) {
+ $transaction->rollBack();
+ throw $e;
+ }
+ } else {
+ $result = $this->updateInternal($attributes);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @see CActiveRecord::update()
+ * @throws StaleObjectException
+ */
+ protected function updateInternal($attributes = null)
+ {
+ if (!$this->beforeSave(false)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ $this->afterSave(false);
+
+ return 0;
+ }
+
+ // Replace is supported only by runtime indexes and necessary only for field update
+ $useReplace = false;
+ $indexSchema = $this->getIndexSchema();
+ if ($this->getIndexSchema()->isRuntime) {
+ foreach ($values as $name => $value) {
+ $columnSchema = $indexSchema->getColumn($name);
+ if ($columnSchema->isField) {
+ $useReplace = true;
+ break;
+ }
+ }
+ }
+
+ if ($useReplace) {
+ $values = array_merge($values, $this->getOldPrimaryKey(true));
+ $command = static::getDb()->createCommand();
+ $command->replace(static::indexName(), $values);
+ // We do not check the return value of replace because it's possible
+ // that the REPLACE statement doesn't change anything and thus returns 0.
+ $rows = $command->execute();
+ } else {
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ if (!isset($values[$lock])) {
+ $values[$lock] = $this->$lock + 1;
+ }
+ $condition[$lock] = $this->$lock;
+ }
+ // We do not check the return value of updateAll() because it's possible
+ // that the UPDATE statement doesn't change anything and thus returns 0.
+ $rows = $this->updateAll($values, $condition);
+
+ if ($lock !== null && !$rows) {
+ throw new StaleObjectException('The object being updated is outdated.');
+ }
+ }
+
+ foreach ($values as $name => $value) {
+ $this->setOldAttribute($name, $this->getAttribute($name));
+ }
+ $this->afterSave(false);
+
+ return $rows;
+ }
+
+ /**
+ * Deletes the index entry corresponding to this active record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 2. delete the record from the index;
+ * 3. call [[afterDelete()]].
+ *
+ * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
+ * will be raised by the corresponding methods.
+ *
+ * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+ * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being deleted is outdated.
+ * @throws \Exception in case delete failed.
+ */
+ public function delete()
+ {
+ $db = static::getDb();
+ $transaction = $this->isTransactional(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
+ try {
+ $result = false;
+ if ($this->beforeDelete()) {
+ // we do not check the return value of deleteAll() because it's possible
+ // the record is already deleted in the database and thus the method will return 0
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ $condition[$lock] = $this->$lock;
+ }
+ $result = $this->deleteAll($condition);
+ if ($lock !== null && !$result) {
+ throw new StaleObjectException('The object being deleted is outdated.');
+ }
+ $this->setOldAttributes(null);
+ $this->afterDelete();
+ }
+ if ($transaction !== null) {
+ if ($result === false) {
+ $transaction->rollBack();
+ } else {
+ $transaction->commit();
+ }
+ }
+ } catch (\Exception $e) {
+ if ($transaction !== null) {
+ $transaction->rollBack();
+ }
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a value indicating whether the given active record is the same as the current one.
+ * The comparison is made by comparing the index names and the primary key values of the two active records.
+ * If one of the records [[isNewRecord|is new]] they are also considered not equal.
+ * @param ActiveRecord $record record to compare to
+ * @return boolean whether the two active records refer to the same row in the same index.
+ */
+ public function equals($record)
+ {
+ if ($this->isNewRecord || $record->isNewRecord) {
+ return false;
+ }
+
+ return $this->indexName() === $record->indexName() && $this->getPrimaryKey() === $record->getPrimaryKey();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function populateRecord($record, $row)
+ {
+ $columns = static::getIndexSchema()->columns;
+ foreach ($row as $name => $value) {
+ if (isset($columns[$name]) && $columns[$name]->isMva) {
+ $row[$name] = explode(',', $value);
+ }
+ }
+ parent::populateRecord($record, $row);
+ }
+
+ /**
+ * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
+ * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
+ * @return boolean whether the specified operation is transactional in the current [[scenario]].
+ */
+ public function isTransactional($operation)
+ {
+ $scenario = $this->getScenario();
+ $transactions = $this->transactions();
+
+ return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
+ }
}
diff --git a/extensions/sphinx/ColumnSchema.php b/extensions/sphinx/ColumnSchema.php
index a7736f772fe..76c66e11934 100644
--- a/extensions/sphinx/ColumnSchema.php
+++ b/extensions/sphinx/ColumnSchema.php
@@ -18,64 +18,65 @@
*/
class ColumnSchema extends Object
{
- /**
- * @var string name of this column (without quotes).
- */
- public $name;
- /**
- * @var string abstract type of this column. Possible abstract types include:
- * string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
- * timestamp, time, date, binary, and money.
- */
- public $type;
- /**
- * @var string the PHP type of this column. Possible PHP types include:
- * string, boolean, integer, double.
- */
- public $phpType;
- /**
- * @var string the DB type of this column. Possible DB types vary according to the type of DBMS.
- */
- public $dbType;
- /**
- * @var boolean whether this column is a primary key
- */
- public $isPrimaryKey;
- /**
- * @var boolean whether this column is an attribute
- */
- public $isAttribute;
- /**
- * @var boolean whether this column is a indexed field
- */
- public $isField;
- /**
- * @var boolean whether this column is a multi value attribute (MVA)
- */
- public $isMva;
+ /**
+ * @var string name of this column (without quotes).
+ */
+ public $name;
+ /**
+ * @var string abstract type of this column. Possible abstract types include:
+ * string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
+ * timestamp, time, date, binary, and money.
+ */
+ public $type;
+ /**
+ * @var string the PHP type of this column. Possible PHP types include:
+ * string, boolean, integer, double.
+ */
+ public $phpType;
+ /**
+ * @var string the DB type of this column. Possible DB types vary according to the type of DBMS.
+ */
+ public $dbType;
+ /**
+ * @var boolean whether this column is a primary key
+ */
+ public $isPrimaryKey;
+ /**
+ * @var boolean whether this column is an attribute
+ */
+ public $isAttribute;
+ /**
+ * @var boolean whether this column is a indexed field
+ */
+ public $isField;
+ /**
+ * @var boolean whether this column is a multi value attribute (MVA)
+ */
+ public $isMva;
- /**
- * Converts the input value according to [[phpType]].
- * If the value is null or an [[Expression]], it will not be converted.
- * @param mixed $value input value
- * @return mixed converted value
- */
- public function typecast($value)
- {
- if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
- return $value;
- }
- if ($value === '' && $this->type !== Schema::TYPE_STRING) {
- return null;
- }
- switch ($this->phpType) {
- case 'string':
- return (string)$value;
- case 'integer':
- return (integer)$value;
- case 'boolean':
- return (boolean)$value;
- }
- return $value;
- }
+ /**
+ * Converts the input value according to [[phpType]].
+ * If the value is null or an [[Expression]], it will not be converted.
+ * @param mixed $value input value
+ * @return mixed converted value
+ */
+ public function typecast($value)
+ {
+ if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
+ return $value;
+ }
+ if ($value === '' && $this->type !== Schema::TYPE_STRING) {
+ return null;
+ }
+ switch ($this->phpType) {
+ case 'string':
+ return (string) $value;
+ case 'integer':
+ return (integer) $value;
+ case 'boolean':
+ return (boolean) $value;
+ }
+
+ return $value;
+ }
}
diff --git a/extensions/sphinx/Command.php b/extensions/sphinx/Command.php
index 9197b67cda9..ae0586d5234 100644
--- a/extensions/sphinx/Command.php
+++ b/extensions/sphinx/Command.php
@@ -44,283 +44,290 @@
*/
class Command extends \yii\db\Command
{
- /**
- * @var \yii\sphinx\Connection the Sphinx connection that this command is associated with.
- */
- public $db;
-
- /**
- * Creates a batch INSERT command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [
- * ['Tom', 30],
- * ['Jane', 20],
- * ['Linda', 25],
- * ])->execute();
- * ~~~
- *
- * Note that the values in each row must match the corresponding column names.
- *
- * @param string $index the index that new rows will be inserted into.
- * @param array $columns the column names
- * @param array $rows the rows to be batch inserted into the index
- * @return static the command object itself
- */
- public function batchInsert($index, $columns, $rows)
- {
- $params = [];
- $sql = $this->db->getQueryBuilder()->batchInsert($index, $columns, $rows, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
- /**
- * Creates an REPLACE command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->insert('idx_user', [
- * 'name' => 'Sam',
- * 'age' => 30,
- * ])->execute();
- * ~~~
- *
- * The method will properly escape the column names, and bind the values to be replaced.
- *
- * Note that the created command is not executed until [[execute()]] is called.
- *
- * @param string $index the index that new rows will be replaced into.
- * @param array $columns the column data (name => value) to be replaced into the index.
- * @return static the command object itself
- */
- public function replace($index, $columns)
- {
- $params = [];
- $sql = $this->db->getQueryBuilder()->replace($index, $columns, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
- /**
- * Creates a batch REPLACE command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [
- * ['Tom', 30],
- * ['Jane', 20],
- * ['Linda', 25],
- * ])->execute();
- * ~~~
- *
- * Note that the values in each row must match the corresponding column names.
- *
- * @param string $index the index that new rows will be replaced.
- * @param array $columns the column names
- * @param array $rows the rows to be batch replaced in the index
- * @return static the command object itself
- */
- public function batchReplace($index, $columns, $rows)
- {
- $params = [];
- $sql = $this->db->getQueryBuilder()->batchReplace($index, $columns, $rows, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
- /**
- * Creates an UPDATE command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute();
- * ~~~
- *
- * The method will properly escape the column names and bind the values to be updated.
- *
- * Note that the created command is not executed until [[execute()]] is called.
- *
- * @param string $index the index to be updated.
- * @param array $columns the column data (name => value) to be updated.
- * @param string|array $condition the condition that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify condition.
- * @param array $params the parameters to be bound to the command
- * @param array $options list of options in format: optionName => optionValue
- * @return static the command object itself
- */
- public function update($index, $columns, $condition = '', $params = [], $options = [])
- {
- $sql = $this->db->getQueryBuilder()->update($index, $columns, $condition, $params, $options);
- return $this->setSql($sql)->bindValues($params);
- }
-
- /**
- * Creates a SQL command for truncating a runtime index.
- * @param string $index the index to be truncated. The name will be properly quoted by the method.
- * @return static the command object itself
- */
- public function truncateIndex($index)
- {
- $sql = $this->db->getQueryBuilder()->truncateIndex($index);
- return $this->setSql($sql);
- }
-
- /**
- * Builds a snippet from provided data and query, using specified index settings.
- * @param string $index name of the index, from which to take the text processing settings.
- * @param string|array $source is the source data to extract a snippet from.
- * It could be either a single string or array of strings.
- * @param string $match the full-text query to build snippets for.
- * @param array $options list of options in format: optionName => optionValue
- * @return static the command object itself
- */
- public function callSnippets($index, $source, $match, $options = [])
- {
- $params = [];
- $sql = $this->db->getQueryBuilder()->callSnippets($index, $source, $match, $options, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
- /**
- * Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics.
- * @param string $index the name of the index from which to take the text processing settings
- * @param string $text the text to break down to keywords.
- * @param boolean $fetchStatistic whether to return document and hit occurrence statistics
- * @return string the SQL statement for call keywords.
- */
- public function callKeywords($index, $text, $fetchStatistic = false)
- {
- $params = [];
- $sql = $this->db->getQueryBuilder()->callKeywords($index, $text, $fetchStatistic, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
- // Not Supported :
-
- /**
- * @inheritdoc
- */
- public function createTable($table, $columns, $options = null)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function renameTable($table, $newName)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function dropTable($table)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function truncateTable($table)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function addColumn($table, $column, $type)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function dropColumn($table, $column)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function renameColumn($table, $oldName, $newName)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function alterColumn($table, $column, $type)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function addPrimaryKey($name, $table, $columns)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function dropPrimaryKey($name, $table)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function dropForeignKey($name, $table)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function createIndex($name, $table, $columns, $unique = false)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function dropIndex($name, $table)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function resetSequence($table, $value = null)
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
-
- /**
- * @inheritdoc
- */
- public function checkIntegrity($check = true, $schema = '', $table = '')
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
+ /**
+ * @var \yii\sphinx\Connection the Sphinx connection that this command is associated with.
+ */
+ public $db;
+
+ /**
+ * Creates a batch INSERT command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [
+ * ['Tom', 30],
+ * ['Jane', 20],
+ * ['Linda', 25],
+ * ])->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $index the index that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the index
+ * @return static the command object itself
+ */
+ public function batchInsert($index, $columns, $rows)
+ {
+ $params = [];
+ $sql = $this->db->getQueryBuilder()->batchInsert($index, $columns, $rows, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates an REPLACE command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->insert('idx_user', [
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * ])->execute();
+ * ~~~
+ *
+ * The method will properly escape the column names, and bind the values to be replaced.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $index the index that new rows will be replaced into.
+ * @param array $columns the column data (name => value) to be replaced into the index.
+ * @return static the command object itself
+ */
+ public function replace($index, $columns)
+ {
+ $params = [];
+ $sql = $this->db->getQueryBuilder()->replace($index, $columns, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates a batch REPLACE command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [
+ * ['Tom', 30],
+ * ['Jane', 20],
+ * ['Linda', 25],
+ * ])->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $index the index that new rows will be replaced.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch replaced in the index
+ * @return static the command object itself
+ */
+ public function batchReplace($index, $columns, $rows)
+ {
+ $params = [];
+ $sql = $this->db->getQueryBuilder()->batchReplace($index, $columns, $rows, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates an UPDATE command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute();
+ * ~~~
+ *
+ * The method will properly escape the column names and bind the values to be updated.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $index the index to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param string|array $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the parameters to be bound to the command
+ * @param array $options list of options in format: optionName => optionValue
+ * @return static the command object itself
+ */
+ public function update($index, $columns, $condition = '', $params = [], $options = [])
+ {
+ $sql = $this->db->getQueryBuilder()->update($index, $columns, $condition, $params, $options);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates a SQL command for truncating a runtime index.
+ * @param string $index the index to be truncated. The name will be properly quoted by the method.
+ * @return static the command object itself
+ */
+ public function truncateIndex($index)
+ {
+ $sql = $this->db->getQueryBuilder()->truncateIndex($index);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Builds a snippet from provided data and query, using specified index settings.
+ * @param string $index name of the index, from which to take the text processing settings.
+ * @param string|array $source is the source data to extract a snippet from.
+ * It could be either a single string or array of strings.
+ * @param string $match the full-text query to build snippets for.
+ * @param array $options list of options in format: optionName => optionValue
+ * @return static the command object itself
+ */
+ public function callSnippets($index, $source, $match, $options = [])
+ {
+ $params = [];
+ $sql = $this->db->getQueryBuilder()->callSnippets($index, $source, $match, $options, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics.
+ * @param string $index the name of the index from which to take the text processing settings
+ * @param string $text the text to break down to keywords.
+ * @param boolean $fetchStatistic whether to return document and hit occurrence statistics
+ * @return string the SQL statement for call keywords.
+ */
+ public function callKeywords($index, $text, $fetchStatistic = false)
+ {
+ $params = [];
+ $sql = $this->db->getQueryBuilder()->callKeywords($index, $text, $fetchStatistic, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ // Not Supported :
+
+ /**
+ * @inheritdoc
+ */
+ public function createTable($table, $columns, $options = null)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function renameTable($table, $newName)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function dropTable($table)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function truncateTable($table)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function addColumn($table, $column, $type)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function dropColumn($table, $column)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function dropForeignKey($name, $table)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createIndex($name, $table, $columns, $unique = false)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function dropIndex($name, $table)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resetSequence($table, $value = null)
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
}
diff --git a/extensions/sphinx/Connection.php b/extensions/sphinx/Connection.php
index c9c67ab6649..3548270e20b 100644
--- a/extensions/sphinx/Connection.php
+++ b/extensions/sphinx/Connection.php
@@ -59,72 +59,73 @@
*/
class Connection extends \yii\db\Connection
{
- /**
- * @inheritdoc
- */
- public $schemaMap = [
- 'mysqli' => 'yii\sphinx\Schema', // MySQL
- 'mysql' => 'yii\sphinx\Schema', // MySQL
- ];
+ /**
+ * @inheritdoc
+ */
+ public $schemaMap = [
+ 'mysqli' => 'yii\sphinx\Schema', // MySQL
+ 'mysql' => 'yii\sphinx\Schema', // MySQL
+ ];
- /**
- * Obtains the schema information for the named index.
- * @param string $name index name.
- * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
- * @return IndexSchema index schema information. Null if the named index does not exist.
- */
- public function getIndexSchema($name, $refresh = false)
- {
- return $this->getSchema()->getIndexSchema($name, $refresh);
- }
+ /**
+ * Obtains the schema information for the named index.
+ * @param string $name index name.
+ * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
+ * @return IndexSchema index schema information. Null if the named index does not exist.
+ */
+ public function getIndexSchema($name, $refresh = false)
+ {
+ return $this->getSchema()->getIndexSchema($name, $refresh);
+ }
- /**
- * Quotes a index name for use in a query.
- * If the index name contains schema prefix, the prefix will also be properly quoted.
- * If the index name is already quoted or contains special characters including '(', '[[' and '{{',
- * then this method will do nothing.
- * @param string $name index name
- * @return string the properly quoted index name
- */
- public function quoteIndexName($name)
- {
- return $this->getSchema()->quoteIndexName($name);
- }
+ /**
+ * Quotes a index name for use in a query.
+ * If the index name contains schema prefix, the prefix will also be properly quoted.
+ * If the index name is already quoted or contains special characters including '(', '[[' and '{{',
+ * then this method will do nothing.
+ * @param string $name index name
+ * @return string the properly quoted index name
+ */
+ public function quoteIndexName($name)
+ {
+ return $this->getSchema()->quoteIndexName($name);
+ }
- /**
- * Alias of [[quoteIndexName()]].
- * @param string $name table name
- * @return string the properly quoted table name
- */
- public function quoteTableName($name)
- {
- return $this->quoteIndexName($name);
- }
+ /**
+ * Alias of [[quoteIndexName()]].
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteTableName($name)
+ {
+ return $this->quoteIndexName($name);
+ }
- /**
- * Creates a command for execution.
- * @param string $sql the SQL statement to be executed
- * @param array $params the parameters to be bound to the SQL statement
- * @return Command the Sphinx command
- */
- public function createCommand($sql = null, $params = [])
- {
- $this->open();
- $command = new Command([
- 'db' => $this,
- 'sql' => $sql,
- ]);
- return $command->bindValues($params);
- }
+ /**
+ * Creates a command for execution.
+ * @param string $sql the SQL statement to be executed
+ * @param array $params the parameters to be bound to the SQL statement
+ * @return Command the Sphinx command
+ */
+ public function createCommand($sql = null, $params = [])
+ {
+ $this->open();
+ $command = new Command([
+ 'db' => $this,
+ 'sql' => $sql,
+ ]);
- /**
- * This method is not supported by Sphinx.
- * @param string $sequenceName name of the sequence object
- * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
- * @throws \yii\base\NotSupportedException always.
- */
- public function getLastInsertID($sequenceName = '')
- {
- throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
- }
+ return $command->bindValues($params);
+ }
+
+ /**
+ * This method is not supported by Sphinx.
+ * @param string $sequenceName name of the sequence object
+ * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
+ * @throws \yii\base\NotSupportedException always.
+ */
+ public function getLastInsertID($sequenceName = '')
+ {
+ throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
+ }
}
diff --git a/extensions/sphinx/IndexSchema.php b/extensions/sphinx/IndexSchema.php
index 4e612195788..073c1e7e5f4 100644
--- a/extensions/sphinx/IndexSchema.php
+++ b/extensions/sphinx/IndexSchema.php
@@ -19,44 +19,44 @@
*/
class IndexSchema extends Object
{
- /**
- * @var string name of this index.
- */
- public $name;
- /**
- * @var string type of the index.
- */
- public $type;
- /**
- * @var boolean whether this index is a runtime index.
- */
- public $isRuntime;
- /**
- * @var string primary key of this index.
- */
- public $primaryKey;
- /**
- * @var ColumnSchema[] column metadata of this index. Each array element is a [[ColumnSchema]] object, indexed by column names.
- */
- public $columns = [];
+ /**
+ * @var string name of this index.
+ */
+ public $name;
+ /**
+ * @var string type of the index.
+ */
+ public $type;
+ /**
+ * @var boolean whether this index is a runtime index.
+ */
+ public $isRuntime;
+ /**
+ * @var string primary key of this index.
+ */
+ public $primaryKey;
+ /**
+ * @var ColumnSchema[] column metadata of this index. Each array element is a [[ColumnSchema]] object, indexed by column names.
+ */
+ public $columns = [];
- /**
- * Gets the named column metadata.
- * This is a convenient method for retrieving a named column even if it does not exist.
- * @param string $name column name
- * @return ColumnSchema metadata of the named column. Null if the named column does not exist.
- */
- public function getColumn($name)
- {
- return isset($this->columns[$name]) ? $this->columns[$name] : null;
- }
+ /**
+ * Gets the named column metadata.
+ * This is a convenient method for retrieving a named column even if it does not exist.
+ * @param string $name column name
+ * @return ColumnSchema metadata of the named column. Null if the named column does not exist.
+ */
+ public function getColumn($name)
+ {
+ return isset($this->columns[$name]) ? $this->columns[$name] : null;
+ }
- /**
- * Returns the names of all columns in this table.
- * @return array list of column names
- */
- public function getColumnNames()
- {
- return array_keys($this->columns);
- }
+ /**
+ * Returns the names of all columns in this table.
+ * @return array list of column names
+ */
+ public function getColumnNames()
+ {
+ return array_keys($this->columns);
+ }
}
diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php
index b9b9abd3894..b75abdde3fd 100644
--- a/extensions/sphinx/Query.php
+++ b/extensions/sphinx/Query.php
@@ -48,670 +48,700 @@
*/
class Query extends Component implements QueryInterface
{
- use QueryTrait;
-
- /**
- * @var array the columns being selected. For example, `['id', 'group_id']`.
- * This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns.
- * @see select()
- */
- public $select;
- /**
- * @var string additional option that should be appended to the 'SELECT' keyword.
- */
- public $selectOption;
- /**
- * @var boolean whether to select distinct rows of data only. If this is set true,
- * the SELECT clause would be changed to SELECT DISTINCT.
- */
- public $distinct;
- /**
- * @var array the index(es) to be selected from. For example, `['idx_user', 'idx_user_delta']`.
- * This is used to construct the FROM clause in a SQL statement.
- * @see from()
- */
- public $from;
- /**
- * @var string text, which should be searched in fulltext mode.
- * This value will be composed into MATCH operator inside the WHERE clause.
- */
- public $match;
- /**
- * @var array how to group the query results. For example, `['company', 'department']`.
- * This is used to construct the GROUP BY clause in a SQL statement.
- */
- public $groupBy;
- /**
- * @var string WITHIN GROUP ORDER BY clause. This is a Sphinx specific extension
- * that lets you control how the best row within a group will to be selected.
- * The possible value matches the [[orderBy]] one.
- */
- public $within;
- /**
- * @var array per-query options in format: optionName => optionValue
- * They will compose OPTION clause. This is a Sphinx specific extension
- * that lets you control a number of per-query options.
- */
- public $options;
- /**
- * @var array list of query parameter values indexed by parameter placeholders.
- * For example, `[':name' => 'Dan', ':age' => 31]`.
- */
- public $params = [];
- /**
- * @var callable PHP callback, which should be used to fetch source data for the snippets.
- * Such callback will receive array of query result rows as an argument and must return the
- * array of snippet source strings in the order, which match one of incoming rows.
- * For example:
- * ~~~
- * $query = new Query;
- * $query->from('idx_item')
- * ->match('pencil')
- * ->snippetCallback(function ($rows) {
- * $result = [];
- * foreach ($rows as $row) {
- * $result[] = file_get_contents('/path/to/index/files/' . $row['id'] . '.txt');
- * }
- * return $result;
- * })
- * ->all();
- * ~~~
- */
- public $snippetCallback;
- /**
- * @var array query options for the call snippet.
- */
- public $snippetOptions;
- /**
- * @var Connection the Sphinx connection used to generate the SQL statements.
- */
- private $_connection;
-
- /**
- * @param Connection $connection Sphinx connection instance
- * @return static the query object itself
- */
- public function setConnection($connection)
- {
- $this->_connection = $connection;
- return $this;
- }
-
- /**
- * @return Connection Sphinx connection instance
- */
- public function getConnection()
- {
- if ($this->_connection === null) {
- $this->_connection = $this->defaultConnection();
- }
- return $this->_connection;
- }
-
- /**
- * @return Connection default connection value.
- */
- protected function defaultConnection()
- {
- return Yii::$app->getComponent('sphinx');
- }
-
- /**
- * Creates a Sphinx command that can be used to execute this query.
- * @param Connection $connection the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return Command the created Sphinx command instance.
- */
- public function createCommand($connection = null)
- {
- $this->setConnection($connection);
- $connection = $this->getConnection();
- list ($sql, $params) = $connection->getQueryBuilder()->build($this);
- return $connection->createCommand($sql, $params);
- }
-
- /**
- * Executes the query and returns all results as an array.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- $rows = $this->createCommand($db)->queryAll();
- $rows = $this->fillUpSnippets($rows);
- if ($this->indexBy === null) {
- return $rows;
- }
- $result = [];
- foreach ($rows as $row) {
- if (is_string($this->indexBy)) {
- $key = $row[$this->indexBy];
- } else {
- $key = call_user_func($this->indexBy, $row);
- }
- $result[$key] = $row;
- }
- return $result;
- }
-
- /**
- * Executes the query and returns a single row of result.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
- * results in nothing.
- */
- public function one($db = null)
- {
- $row = $this->createCommand($db)->queryOne();
- if ($row !== false) {
- list ($row) = $this->fillUpSnippets([$row]);
- }
- return $row;
- }
-
- /**
- * Returns the query result as a scalar value.
- * The value returned will be the first column in the first row of the query results.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return string|boolean the value of the first column in the first row of the query result.
- * False is returned if the query result is empty.
- */
- public function scalar($db = null)
- {
- return $this->createCommand($db)->queryScalar();
- }
-
- /**
- * Executes the query and returns the first column of the result.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return array the first column of the query result. An empty array is returned if the query results in nothing.
- */
- public function column($db = null)
- {
- return $this->createCommand($db)->queryColumn();
- }
-
- /**
- * Returns the number of records.
- * @param string $q the COUNT expression. Defaults to '*'.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return integer number of records
- */
- public function count($q = '*', $db = null)
- {
- $this->select = ["COUNT($q)"];
- return $this->createCommand($db)->queryScalar();
- }
-
- /**
- * Returns the sum of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return integer the sum of the specified column values
- */
- public function sum($q, $db = null)
- {
- $this->select = ["SUM($q)"];
- return $this->createCommand($db)->queryScalar();
- }
-
- /**
- * Returns the average of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return integer the average of the specified column values.
- */
- public function average($q, $db = null)
- {
- $this->select = ["AVG($q)"];
- return $this->createCommand($db)->queryScalar();
- }
-
- /**
- * Returns the minimum of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return integer the minimum of the specified column values.
- */
- public function min($q, $db = null)
- {
- $this->select = ["MIN($q)"];
- return $this->createCommand($db)->queryScalar();
- }
-
- /**
- * Returns the maximum of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return integer the maximum of the specified column values.
- */
- public function max($q, $db = null)
- {
- $this->select = ["MAX($q)"];
- return $this->createCommand($db)->queryScalar();
- }
-
- /**
- * Returns a value indicating whether the query result contains any row of data.
- * @param Connection $db the Sphinx connection used to generate the SQL statement.
- * If this parameter is not given, the `sphinx` application component will be used.
- * @return boolean whether the query result contains any row of data.
- */
- public function exists($db = null)
- {
- $this->select = [new Expression('1')];
- return $this->scalar($db) !== false;
- }
-
- /**
- * Sets the SELECT part of the query.
- * @param string|array $columns the columns to be selected.
- * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a Sphinx expression).
- * @param string $option additional option that should be appended to the 'SELECT' keyword.
- * @return static the query object itself
- */
- public function select($columns, $option = null)
- {
- if (!is_array($columns)) {
- $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
- }
- $this->select = $columns;
- $this->selectOption = $option;
- return $this;
- }
-
- /**
- * Sets the value indicating whether to SELECT DISTINCT or not.
- * @param boolean $value whether to SELECT DISTINCT or not.
- * @return static the query object itself
- */
- public function distinct($value = true)
- {
- $this->distinct = $value;
- return $this;
- }
-
- /**
- * Sets the FROM part of the query.
- * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'idx_user'`)
- * or an array (e.g. `['idx_user', 'idx_user_delta']`) specifying one or several index names.
- * The method will automatically quote the table names unless it contains some parenthesis
- * (which means the table is given as a sub-query or Sphinx expression).
- * @return static the query object itself
- */
- public function from($tables)
- {
- if (!is_array($tables)) {
- $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
- }
- $this->from = $tables;
- return $this;
- }
-
- /**
- * Sets the fulltext query text. This text will be composed into
- * MATCH operator inside the WHERE clause.
- * @param string $query fulltext query text.
- * @return static the query object itself
- */
- public function match($query)
- {
- $this->match = $query;
- return $this;
- }
-
- /**
- * Sets the WHERE part of the query.
- *
- * The method requires a $condition parameter, and optionally a $params parameter
- * specifying the values to be bound to the query.
- *
- * The $condition parameter should be either a string (e.g. 'id=1') or an array.
- * If the latter, it must be in one of the following two formats:
- *
- * - hash format: `['column1' => value1, 'column2' => value2, ...]`
- * - operator format: `[operator, operand1, operand2, ...]`
- *
- * A condition in hash format represents the following SQL expression in general:
- * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
- * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
- * in the generated expression. Below are some examples:
- *
- * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`.
- * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`.
- * - `['status' => null] generates `status IS NULL`.
- *
- * A condition in operator format generates the SQL expression according to the specified operator, which
- * can be one of the followings:
- *
- * - `and`: the operands should be concatenated together using `AND`. For example,
- * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
- * it will be converted into a string using the rules described here. For example,
- * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
- * The method will NOT do any quoting or escaping.
- *
- * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
- *
- * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
- * starting and ending values of the range that the column is in.
- * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
- *
- * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
- * in the generated condition.
- *
- * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
- * the range of the values that the column or DB expression should be in. For example,
- * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
- * The method will properly quote the column name and escape values in the range.
- *
- * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
- *
- * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
- * the values that the column or DB expression should be like.
- * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`.
- * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
- * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate
- * `name LIKE '%test%' AND name LIKE '%sample%'`.
- * The method will properly quote the column name and escape values in the range.
- * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
- * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
- *
- * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
- * predicates when operand 2 is an array.
- *
- * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
- * in the generated condition.
- *
- * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
- * the `NOT LIKE` predicates.
- *
- * @param string|array $condition the conditions that should be put in the WHERE part.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see andWhere()
- * @see orWhere()
- */
- public function where($condition, $params = [])
- {
- $this->where = $condition;
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'AND' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see where()
- * @see orWhere()
- */
- public function andWhere($condition, $params = [])
- {
- if ($this->where === null) {
- $this->where = $condition;
- } else {
- $this->where = ['and', $this->where, $condition];
- }
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'OR' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see where()
- * @see andWhere()
- */
- public function orWhere($condition, $params = [])
- {
- if ($this->where === null) {
- $this->where = $condition;
- } else {
- $this->where = ['or', $this->where, $condition];
- }
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Sets the GROUP BY part of the query.
- * @param string|array $columns the columns to be grouped by.
- * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see addGroupBy()
- */
- public function groupBy($columns)
- {
- if (!is_array($columns)) {
- $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
- }
- $this->groupBy = $columns;
- return $this;
- }
-
- /**
- * Adds additional group-by columns to the existing ones.
- * @param string|array $columns additional columns to be grouped by.
- * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see groupBy()
- */
- public function addGroupBy($columns)
- {
- if (!is_array($columns)) {
- $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
- }
- if ($this->groupBy === null) {
- $this->groupBy = $columns;
- } else {
- $this->groupBy = array_merge($this->groupBy, $columns);
- }
- return $this;
- }
-
- /**
- * Sets the parameters to be bound to the query.
- * @param array $params list of query parameter values indexed by parameter placeholders.
- * For example, `[':name' => 'Dan', ':age' => 31]`.
- * @return static the query object itself
- * @see addParams()
- */
- public function params($params)
- {
- $this->params = $params;
- return $this;
- }
-
- /**
- * Adds additional parameters to be bound to the query.
- * @param array $params list of query parameter values indexed by parameter placeholders.
- * For example, `[':name' => 'Dan', ':age' => 31]`.
- * @return static the query object itself
- * @see params()
- */
- public function addParams($params)
- {
- if (!empty($params)) {
- if (empty($this->params)) {
- $this->params = $params;
- } else {
- foreach ($params as $name => $value) {
- if (is_integer($name)) {
- $this->params[] = $value;
- } else {
- $this->params[$name] = $value;
- }
- }
- }
- }
- return $this;
- }
-
- /**
- * Sets the query options.
- * @param array $options query options in format: optionName => optionValue
- * @return static the query object itself
- * @see addOptions()
- */
- public function options($options)
- {
- $this->options = $options;
- return $this;
- }
-
- /**
- * Adds additional query options.
- * @param array $options query options in format: optionName => optionValue
- * @return static the query object itself
- * @see options()
- */
- public function addOptions($options)
- {
- if (is_array($this->options)) {
- $this->options = array_merge($this->options, $options);
- } else {
- $this->options = $options;
- }
- return $this;
- }
-
- /**
- * Sets the WITHIN GROUP ORDER BY part of the query.
- * @param string|array $columns the columns (and the directions) to find best row within a group.
- * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see addWithin()
- */
- public function within($columns)
- {
- $this->within = $this->normalizeOrderBy($columns);
- return $this;
- }
-
- /**
- * Adds additional WITHIN GROUP ORDER BY columns to the query.
- * @param string|array $columns the columns (and the directions) to find best row within a group.
- * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see within()
- */
- public function addWithin($columns)
- {
- $columns = $this->normalizeOrderBy($columns);
- if ($this->within === null) {
- $this->within = $columns;
- } else {
- $this->within = array_merge($this->within, $columns);
- }
- return $this;
- }
-
- /**
- * Sets the PHP callback, which should be used to retrieve the source data
- * for the snippets building.
- * @param callable $callback PHP callback, which should be used to fetch source data for the snippets.
- * @return static the query object itself
- * @see snippetCallback
- */
- public function snippetCallback($callback)
- {
- $this->snippetCallback = $callback;
- return $this;
- }
-
- /**
- * Sets the call snippets query options.
- * @param array $options call snippet options in format: option_name => option_value
- * @return static the query object itself
- * @see snippetCallback
- */
- public function snippetOptions($options)
- {
- $this->snippetOptions = $options;
- return $this;
- }
-
- /**
- * Fills the query result rows with the snippets built from source determined by
- * [[snippetCallback]] result.
- * @param array $rows raw query result rows.
- * @return array|ActiveRecord[] query result rows with filled up snippets.
- */
- protected function fillUpSnippets($rows)
- {
- if ($this->snippetCallback === null) {
- return $rows;
- }
- $snippetSources = call_user_func($this->snippetCallback, $rows);
- $snippets = $this->callSnippets($snippetSources);
- $snippetKey = 0;
- foreach ($rows as $key => $row) {
- $rows[$key]['snippet'] = $snippets[$snippetKey];
- $snippetKey++;
- }
- return $rows;
- }
-
- /**
- * Builds a snippets from provided source data.
- * @param array $source the source data to extract a snippet from.
- * @throws InvalidCallException in case [[match]] is not specified.
- * @return array snippets list.
- */
- protected function callSnippets(array $source)
- {
- return $this->callSnippetsInternal($source, $this->from[0]);
- }
-
- /**
- * Builds a snippets from provided source data by the given index.
- * @param array $source the source data to extract a snippet from.
- * @param string $from name of the source index.
- * @return array snippets list.
- * @throws InvalidCallException in case [[match]] is not specified.
- */
- protected function callSnippetsInternal(array $source, $from)
- {
- $connection = $this->getConnection();
- $match = $this->match;
- if ($match === null) {
- throw new InvalidCallException('Unable to call snippets: "' . $this->className() . '::match" should be specified.');
- }
- return $connection->createCommand()
- ->callSnippets($from, $source, $match, $this->snippetOptions)
- ->queryColumn();
- }
+ use QueryTrait;
+
+ /**
+ * @var array the columns being selected. For example, `['id', 'group_id']`.
+ * This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns.
+ * @see select()
+ */
+ public $select;
+ /**
+ * @var string additional option that should be appended to the 'SELECT' keyword.
+ */
+ public $selectOption;
+ /**
+ * @var boolean whether to select distinct rows of data only. If this is set true,
+ * the SELECT clause would be changed to SELECT DISTINCT.
+ */
+ public $distinct;
+ /**
+ * @var array the index(es) to be selected from. For example, `['idx_user', 'idx_user_delta']`.
+ * This is used to construct the FROM clause in a SQL statement.
+ * @see from()
+ */
+ public $from;
+ /**
+ * @var string text, which should be searched in fulltext mode.
+ * This value will be composed into MATCH operator inside the WHERE clause.
+ */
+ public $match;
+ /**
+ * @var array how to group the query results. For example, `['company', 'department']`.
+ * This is used to construct the GROUP BY clause in a SQL statement.
+ */
+ public $groupBy;
+ /**
+ * @var string WITHIN GROUP ORDER BY clause. This is a Sphinx specific extension
+ * that lets you control how the best row within a group will to be selected.
+ * The possible value matches the [[orderBy]] one.
+ */
+ public $within;
+ /**
+ * @var array per-query options in format: optionName => optionValue
+ * They will compose OPTION clause. This is a Sphinx specific extension
+ * that lets you control a number of per-query options.
+ */
+ public $options;
+ /**
+ * @var array list of query parameter values indexed by parameter placeholders.
+ * For example, `[':name' => 'Dan', ':age' => 31]`.
+ */
+ public $params = [];
+ /**
+ * @var callable PHP callback, which should be used to fetch source data for the snippets.
+ * Such callback will receive array of query result rows as an argument and must return the
+ * array of snippet source strings in the order, which match one of incoming rows.
+ * For example:
+ * ~~~
+ * $query = new Query;
+ * $query->from('idx_item')
+ * ->match('pencil')
+ * ->snippetCallback(function ($rows) {
+ * $result = [];
+ * foreach ($rows as $row) {
+ * $result[] = file_get_contents('/path/to/index/files/' . $row['id'] . '.txt');
+ * }
+ * return $result;
+ * })
+ * ->all();
+ * ~~~
+ */
+ public $snippetCallback;
+ /**
+ * @var array query options for the call snippet.
+ */
+ public $snippetOptions;
+ /**
+ * @var Connection the Sphinx connection used to generate the SQL statements.
+ */
+ private $_connection;
+
+ /**
+ * @param Connection $connection Sphinx connection instance
+ * @return static the query object itself
+ */
+ public function setConnection($connection)
+ {
+ $this->_connection = $connection;
+
+ return $this;
+ }
+
+ /**
+ * @return Connection Sphinx connection instance
+ */
+ public function getConnection()
+ {
+ if ($this->_connection === null) {
+ $this->_connection = $this->defaultConnection();
+ }
+
+ return $this->_connection;
+ }
+
+ /**
+ * @return Connection default connection value.
+ */
+ protected function defaultConnection()
+ {
+ return Yii::$app->getComponent('sphinx');
+ }
+
+ /**
+ * Creates a Sphinx command that can be used to execute this query.
+ * @param Connection $connection the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return Command the created Sphinx command instance.
+ */
+ public function createCommand($connection = null)
+ {
+ $this->setConnection($connection);
+ $connection = $this->getConnection();
+ list ($sql, $params) = $connection->getQueryBuilder()->build($this);
+
+ return $connection->createCommand($sql, $params);
+ }
+
+ /**
+ * Executes the query and returns all results as an array.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $rows = $this->createCommand($db)->queryAll();
+ $rows = $this->fillUpSnippets($rows);
+ if ($this->indexBy === null) {
+ return $rows;
+ }
+ $result = [];
+ foreach ($rows as $row) {
+ if (is_string($this->indexBy)) {
+ $key = $row[$this->indexBy];
+ } else {
+ $key = call_user_func($this->indexBy, $row);
+ }
+ $result[$key] = $row;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the query and returns a single row of result.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ */
+ public function one($db = null)
+ {
+ $row = $this->createCommand($db)->queryOne();
+ if ($row !== false) {
+ list ($row) = $this->fillUpSnippets([$row]);
+ }
+
+ return $row;
+ }
+
+ /**
+ * Returns the query result as a scalar value.
+ * The value returned will be the first column in the first row of the query results.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return string|boolean the value of the first column in the first row of the query result.
+ * False is returned if the query result is empty.
+ */
+ public function scalar($db = null)
+ {
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Executes the query and returns the first column of the result.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return array the first column of the query result. An empty array is returned if the query results in nothing.
+ */
+ public function column($db = null)
+ {
+ return $this->createCommand($db)->queryColumn();
+ }
+
+ /**
+ * Returns the number of records.
+ * @param string $q the COUNT expression. Defaults to '*'.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return integer number of records
+ */
+ public function count($q = '*', $db = null)
+ {
+ $this->select = ["COUNT($q)"];
+
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the sum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return integer the sum of the specified column values
+ */
+ public function sum($q, $db = null)
+ {
+ $this->select = ["SUM($q)"];
+
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the average of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return integer the average of the specified column values.
+ */
+ public function average($q, $db = null)
+ {
+ $this->select = ["AVG($q)"];
+
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the minimum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return integer the minimum of the specified column values.
+ */
+ public function min($q, $db = null)
+ {
+ $this->select = ["MIN($q)"];
+
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the maximum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return integer the maximum of the specified column values.
+ */
+ public function max($q, $db = null)
+ {
+ $this->select = ["MAX($q)"];
+
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns a value indicating whether the query result contains any row of data.
+ * @param Connection $db the Sphinx connection used to generate the SQL statement.
+ * If this parameter is not given, the `sphinx` application component will be used.
+ * @return boolean whether the query result contains any row of data.
+ */
+ public function exists($db = null)
+ {
+ $this->select = [new Expression('1')];
+
+ return $this->scalar($db) !== false;
+ }
+
+ /**
+ * Sets the SELECT part of the query.
+ * @param string|array $columns the columns to be selected.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a Sphinx expression).
+ * @param string $option additional option that should be appended to the 'SELECT' keyword.
+ * @return static the query object itself
+ */
+ public function select($columns, $option = null)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->select = $columns;
+ $this->selectOption = $option;
+
+ return $this;
+ }
+
+ /**
+ * Sets the value indicating whether to SELECT DISTINCT or not.
+ * @param boolean $value whether to SELECT DISTINCT or not.
+ * @return static the query object itself
+ */
+ public function distinct($value = true)
+ {
+ $this->distinct = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the FROM part of the query.
+ * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'idx_user'`)
+ * or an array (e.g. `['idx_user', 'idx_user_delta']`) specifying one or several index names.
+ * The method will automatically quote the table names unless it contains some parenthesis
+ * (which means the table is given as a sub-query or Sphinx expression).
+ * @return static the query object itself
+ */
+ public function from($tables)
+ {
+ if (!is_array($tables)) {
+ $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->from = $tables;
+
+ return $this;
+ }
+
+ /**
+ * Sets the fulltext query text. This text will be composed into
+ * MATCH operator inside the WHERE clause.
+ * @param string $query fulltext query text.
+ * @return static the query object itself
+ */
+ public function match($query)
+ {
+ $this->match = $query;
+
+ return $this;
+ }
+
+ /**
+ * Sets the WHERE part of the query.
+ *
+ * The method requires a $condition parameter, and optionally a $params parameter
+ * specifying the values to be bound to the query.
+ *
+ * The $condition parameter should be either a string (e.g. 'id=1') or an array.
+ * If the latter, it must be in one of the following two formats:
+ *
+ * - hash format: `['column1' => value1, 'column2' => value2, ...]`
+ * - operator format: `[operator, operand1, operand2, ...]`
+ *
+ * A condition in hash format represents the following SQL expression in general:
+ * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
+ * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
+ * in the generated expression. Below are some examples:
+ *
+ * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`.
+ * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`.
+ * - `['status' => null] generates `status IS NULL`.
+ *
+ * A condition in operator format generates the SQL expression according to the specified operator, which
+ * can be one of the followings:
+ *
+ * - `and`: the operands should be concatenated together using `AND`. For example,
+ * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
+ * it will be converted into a string using the rules described here. For example,
+ * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
+ * The method will NOT do any quoting or escaping.
+ *
+ * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
+ *
+ * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
+ * starting and ending values of the range that the column is in.
+ * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
+ *
+ * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
+ * in the generated condition.
+ *
+ * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
+ * the range of the values that the column or DB expression should be in. For example,
+ * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
+ * The method will properly quote the column name and escape values in the range.
+ *
+ * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
+ *
+ * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
+ * the values that the column or DB expression should be like.
+ * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`.
+ * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
+ * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate
+ * `name LIKE '%test%' AND name LIKE '%sample%'`.
+ * The method will properly quote the column name and escape values in the range.
+ * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
+ * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
+ *
+ * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
+ * predicates when operand 2 is an array.
+ *
+ * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
+ * in the generated condition.
+ *
+ * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
+ * the `NOT LIKE` predicates.
+ *
+ * @param string|array $condition the conditions that should be put in the WHERE part.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see andWhere()
+ * @see orWhere()
+ */
+ public function where($condition, $params = [])
+ {
+ $this->where = $condition;
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'AND' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see where()
+ * @see orWhere()
+ */
+ public function andWhere($condition, $params = [])
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = ['and', $this->where, $condition];
+ }
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'OR' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see where()
+ * @see andWhere()
+ */
+ public function orWhere($condition, $params = [])
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = ['or', $this->where, $condition];
+ }
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Sets the GROUP BY part of the query.
+ * @param string|array $columns the columns to be grouped by.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see addGroupBy()
+ */
+ public function groupBy($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->groupBy = $columns;
+
+ return $this;
+ }
+
+ /**
+ * Adds additional group-by columns to the existing ones.
+ * @param string|array $columns additional columns to be grouped by.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see groupBy()
+ */
+ public function addGroupBy($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ if ($this->groupBy === null) {
+ $this->groupBy = $columns;
+ } else {
+ $this->groupBy = array_merge($this->groupBy, $columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the parameters to be bound to the query.
+ * @param array $params list of query parameter values indexed by parameter placeholders.
+ * For example, `[':name' => 'Dan', ':age' => 31]`.
+ * @return static the query object itself
+ * @see addParams()
+ */
+ public function params($params)
+ {
+ $this->params = $params;
+
+ return $this;
+ }
+
+ /**
+ * Adds additional parameters to be bound to the query.
+ * @param array $params list of query parameter values indexed by parameter placeholders.
+ * For example, `[':name' => 'Dan', ':age' => 31]`.
+ * @return static the query object itself
+ * @see params()
+ */
+ public function addParams($params)
+ {
+ if (!empty($params)) {
+ if (empty($this->params)) {
+ $this->params = $params;
+ } else {
+ foreach ($params as $name => $value) {
+ if (is_integer($name)) {
+ $this->params[] = $value;
+ } else {
+ $this->params[$name] = $value;
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the query options.
+ * @param array $options query options in format: optionName => optionValue
+ * @return static the query object itself
+ * @see addOptions()
+ */
+ public function options($options)
+ {
+ $this->options = $options;
+
+ return $this;
+ }
+
+ /**
+ * Adds additional query options.
+ * @param array $options query options in format: optionName => optionValue
+ * @return static the query object itself
+ * @see options()
+ */
+ public function addOptions($options)
+ {
+ if (is_array($this->options)) {
+ $this->options = array_merge($this->options, $options);
+ } else {
+ $this->options = $options;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the WITHIN GROUP ORDER BY part of the query.
+ * @param string|array $columns the columns (and the directions) to find best row within a group.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see addWithin()
+ */
+ public function within($columns)
+ {
+ $this->within = $this->normalizeOrderBy($columns);
+
+ return $this;
+ }
+
+ /**
+ * Adds additional WITHIN GROUP ORDER BY columns to the query.
+ * @param string|array $columns the columns (and the directions) to find best row within a group.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see within()
+ */
+ public function addWithin($columns)
+ {
+ $columns = $this->normalizeOrderBy($columns);
+ if ($this->within === null) {
+ $this->within = $columns;
+ } else {
+ $this->within = array_merge($this->within, $columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the PHP callback, which should be used to retrieve the source data
+ * for the snippets building.
+ * @param callable $callback PHP callback, which should be used to fetch source data for the snippets.
+ * @return static the query object itself
+ * @see snippetCallback
+ */
+ public function snippetCallback($callback)
+ {
+ $this->snippetCallback = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Sets the call snippets query options.
+ * @param array $options call snippet options in format: option_name => option_value
+ * @return static the query object itself
+ * @see snippetCallback
+ */
+ public function snippetOptions($options)
+ {
+ $this->snippetOptions = $options;
+
+ return $this;
+ }
+
+ /**
+ * Fills the query result rows with the snippets built from source determined by
+ * [[snippetCallback]] result.
+ * @param array $rows raw query result rows.
+ * @return array|ActiveRecord[] query result rows with filled up snippets.
+ */
+ protected function fillUpSnippets($rows)
+ {
+ if ($this->snippetCallback === null) {
+ return $rows;
+ }
+ $snippetSources = call_user_func($this->snippetCallback, $rows);
+ $snippets = $this->callSnippets($snippetSources);
+ $snippetKey = 0;
+ foreach ($rows as $key => $row) {
+ $rows[$key]['snippet'] = $snippets[$snippetKey];
+ $snippetKey++;
+ }
+
+ return $rows;
+ }
+
+ /**
+ * Builds a snippets from provided source data.
+ * @param array $source the source data to extract a snippet from.
+ * @throws InvalidCallException in case [[match]] is not specified.
+ * @return array snippets list.
+ */
+ protected function callSnippets(array $source)
+ {
+ return $this->callSnippetsInternal($source, $this->from[0]);
+ }
+
+ /**
+ * Builds a snippets from provided source data by the given index.
+ * @param array $source the source data to extract a snippet from.
+ * @param string $from name of the source index.
+ * @return array snippets list.
+ * @throws InvalidCallException in case [[match]] is not specified.
+ */
+ protected function callSnippetsInternal(array $source, $from)
+ {
+ $connection = $this->getConnection();
+ $match = $this->match;
+ if ($match === null) {
+ throw new InvalidCallException('Unable to call snippets: "' . $this->className() . '::match" should be specified.');
+ }
+
+ return $connection->createCommand()
+ ->callSnippets($from, $source, $match, $this->snippetOptions)
+ ->queryColumn();
+ }
}
diff --git a/extensions/sphinx/QueryBuilder.php b/extensions/sphinx/QueryBuilder.php
index 6f99bc8d2a2..9bb1adcce6d 100644
--- a/extensions/sphinx/QueryBuilder.php
+++ b/extensions/sphinx/QueryBuilder.php
@@ -23,922 +23,940 @@
*/
class QueryBuilder extends Object
{
- /**
- * The prefix for automatically generated query binding parameters.
- */
- const PARAM_PREFIX = ':qp';
-
- /**
- * @var Connection the Sphinx connection.
- */
- public $db;
- /**
- * @var string the separator between different fragments of a SQL statement.
- * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
- */
- public $separator = " ";
-
- /**
- * Constructor.
- * @param Connection $connection the Sphinx connection.
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($connection, $config = [])
- {
- $this->db = $connection;
- parent::__construct($config);
- }
-
- /**
- * Generates a SELECT SQL statement from a [[Query]] object.
- * @param Query $query the [[Query]] object from which the SQL statement will be generated
- * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
- * be included in the result with the additional parameters generated during the query building process.
- * @return array the generated SQL statement (the first array element) and the corresponding
- * parameters to be bound to the SQL statement (the second array element). The parameters returned
- * include those provided in `$params`.
- */
- public function build($query, $params = [])
- {
- $params = empty($params) ? $query->params : array_merge($params, $query->params);
-
- if ($query->match !== null) {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = (string)$query->match;
- $query->andWhere('MATCH(' . $phName . ')');
- }
-
- $from = $query->from;
- if ($from === null && $query instanceof ActiveQuery) {
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $from = [$modelClass::indexName()];
- }
-
- $clauses = [
- $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
- $this->buildFrom($from, $params),
- $this->buildWhere($query->from, $query->where, $params),
- $this->buildGroupBy($query->groupBy),
- $this->buildWithin($query->within),
- $this->buildOrderBy($query->orderBy),
- $this->buildLimit($query->limit, $query->offset),
- $this->buildOption($query->options, $params),
- ];
- return [implode($this->separator, array_filter($clauses)), $params];
- }
-
- /**
- * Creates an INSERT SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->insert('idx_user', [
- * 'name' => 'Sam',
- * 'age' => 30,
- * 'id' => 10,
- * ], $params);
- * ~~~
- *
- * The method will properly escape the index and column names.
- *
- * @param string $index the index that new rows will be inserted into.
- * @param array $columns the column data (name => value) to be inserted into the index.
- * @param array $params the binding parameters that will be generated by this method.
- * They should be bound to the Sphinx command later.
- * @return string the INSERT SQL
- */
- public function insert($index, $columns, &$params)
- {
- return $this->generateInsertReplace('INSERT', $index, $columns, $params);
- }
-
- /**
- * Creates an REPLACE SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->replace('idx_user', [
- * 'name' => 'Sam',
- * 'age' => 30,
- * 'id' => 10,
- * ], $params);
- * ~~~
- *
- * The method will properly escape the index and column names.
- *
- * @param string $index the index that new rows will be replaced.
- * @param array $columns the column data (name => value) to be replaced in the index.
- * @param array $params the binding parameters that will be generated by this method.
- * They should be bound to the Sphinx command later.
- * @return string the INSERT SQL
- */
- public function replace($index, $columns, &$params)
- {
- return $this->generateInsertReplace('REPLACE', $index, $columns, $params);
- }
-
- /**
- * Generates INSERT/REPLACE SQL statement.
- * @param string $statement statement ot be generated.
- * @param string $index the affected index name.
- * @param array $columns the column data (name => value).
- * @param array $params the binding parameters that will be generated by this method.
- * @return string generated SQL
- */
- protected function generateInsertReplace($statement, $index, $columns, &$params)
- {
- if (($indexSchema = $this->db->getIndexSchema($index)) !== null) {
- $indexSchemas = [$indexSchema];
- } else {
- $indexSchemas = [];
- }
- $names = [];
- $placeholders = [];
- foreach ($columns as $name => $value) {
- $names[] = $this->db->quoteColumnName($name);
- $placeholders[] = $this->composeColumnValue($indexSchemas, $name, $value, $params);
- }
- return $statement . ' INTO ' . $this->db->quoteIndexName($index)
- . ' (' . implode(', ', $names) . ') VALUES ('
- . implode(', ', $placeholders) . ')';
- }
-
- /**
- * Generates a batch INSERT SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->batchInsert('idx_user', ['id', 'name', 'age'], [
- * [1, 'Tom', 30],
- * [2, 'Jane', 20],
- * [3, 'Linda', 25],
- * ], $params);
- * ~~~
- *
- * Note that the values in each row must match the corresponding column names.
- *
- * @param string $index the index that new rows will be inserted into.
- * @param array $columns the column names
- * @param array $rows the rows to be batch inserted into the index
- * @param array $params the binding parameters that will be generated by this method.
- * They should be bound to the Sphinx command later.
- * @return string the batch INSERT SQL statement
- */
- public function batchInsert($index, $columns, $rows, &$params)
- {
- return $this->generateBatchInsertReplace('INSERT', $index, $columns, $rows, $params);
- }
-
- /**
- * Generates a batch REPLACE SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->batchReplace('idx_user', ['id', 'name', 'age'], [
- * [1, 'Tom', 30],
- * [2, 'Jane', 20],
- * [3, 'Linda', 25],
- * ], $params);
- * ~~~
- *
- * Note that the values in each row must match the corresponding column names.
- *
- * @param string $index the index that new rows will be replaced.
- * @param array $columns the column names
- * @param array $rows the rows to be batch replaced in the index
- * @param array $params the binding parameters that will be generated by this method.
- * They should be bound to the Sphinx command later.
- * @return string the batch INSERT SQL statement
- */
- public function batchReplace($index, $columns, $rows, &$params)
- {
- return $this->generateBatchInsertReplace('REPLACE', $index, $columns, $rows, $params);
- }
-
- /**
- * Generates a batch INSERT/REPLACE SQL statement.
- * @param string $statement statement ot be generated.
- * @param string $index the affected index name.
- * @param array $columns the column data (name => value).
- * @param array $rows the rows to be batch inserted into the index
- * @param array $params the binding parameters that will be generated by this method.
- * @return string generated SQL
- */
- protected function generateBatchInsertReplace($statement, $index, $columns, $rows, &$params)
- {
- if (($indexSchema = $this->db->getIndexSchema($index)) !== null) {
- $indexSchemas = [$indexSchema];
- } else {
- $indexSchemas = [];
- }
-
- foreach ($columns as $i => $name) {
- $columns[$i] = $this->db->quoteColumnName($name);
- }
-
- $values = [];
- foreach ($rows as $row) {
- $vs = [];
- foreach ($row as $i => $value) {
- $vs[] = $this->composeColumnValue($indexSchemas, $columns[$i], $value, $params);
- }
- $values[] = '(' . implode(', ', $vs) . ')';
- }
-
- return $statement . ' INTO ' . $this->db->quoteIndexName($index)
- . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
- }
-
- /**
- * Creates an UPDATE SQL statement.
- * For example,
- *
- * ~~~
- * $params = [];
- * $sql = $queryBuilder->update('idx_user', ['status' => 1], 'age > 30', $params);
- * ~~~
- *
- * The method will properly escape the index and column names.
- *
- * @param string $index the index to be updated.
- * @param array $columns the column data (name => value) to be updated.
- * @param array|string $condition the condition that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify condition.
- * @param array $params the binding parameters that will be modified by this method
- * so that they can be bound to the Sphinx command later.
- * @param array $options list of options in format: optionName => optionValue
- * @return string the UPDATE SQL
- */
- public function update($index, $columns, $condition, &$params, $options)
- {
- if (($indexSchema = $this->db->getIndexSchema($index)) !== null) {
- $indexSchemas = [$indexSchema];
- } else {
- $indexSchemas = [];
- }
-
- $lines = [];
- foreach ($columns as $name => $value) {
- $lines[] = $this->db->quoteColumnName($name) . '=' . $this->composeColumnValue($indexSchemas, $name, $value, $params);
- }
-
- $sql = 'UPDATE ' . $this->db->quoteIndexName($index) . ' SET ' . implode(', ', $lines);
- $where = $this->buildWhere([$index], $condition, $params);
- if ($where !== '') {
- $sql = $sql . ' ' . $where;
- }
- $option = $this->buildOption($options, $params);
- if ($option !== '') {
- $sql = $sql . ' ' . $option;
- }
- return $sql;
- }
-
- /**
- * Creates a DELETE SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->delete('idx_user', 'status = 0');
- * ~~~
- *
- * The method will properly escape the index and column names.
- *
- * @param string $index the index where the data will be deleted from.
- * @param array|string $condition the condition that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify condition.
- * @param array $params the binding parameters that will be modified by this method
- * so that they can be bound to the Sphinx command later.
- * @return string the DELETE SQL
- */
- public function delete($index, $condition, &$params)
- {
- $sql = 'DELETE FROM ' . $this->db->quoteIndexName($index);
- $where = $this->buildWhere([$index], $condition, $params);
- return $where === '' ? $sql : $sql . ' ' . $where;
- }
-
- /**
- * Builds a SQL statement for truncating an index.
- * @param string $index the index to be truncated. The name will be properly quoted by the method.
- * @return string the SQL statement for truncating an index.
- */
- public function truncateIndex($index)
- {
- return 'TRUNCATE RTINDEX ' . $this->db->quoteIndexName($index);
- }
-
- /**
- * Builds a SQL statement for call snippet from provided data and query, using specified index settings.
- * @param string $index name of the index, from which to take the text processing settings.
- * @param string|array $source is the source data to extract a snippet from.
- * It could be either a single string or array of strings.
- * @param string $match the full-text query to build snippets for.
- * @param array $options list of options in format: optionName => optionValue
- * @param array $params the binding parameters that will be modified by this method
- * so that they can be bound to the Sphinx command later.
- * @return string the SQL statement for call snippets.
- */
- public function callSnippets($index, $source, $match, $options, &$params)
- {
- if (is_array($source)) {
- $dataSqlParts = [];
- foreach ($source as $sourceRow) {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = $sourceRow;
- $dataSqlParts[] = $phName;
- }
- $dataSql = '(' . implode(',', $dataSqlParts) . ')';
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = $source;
- $dataSql = $phName;
- }
- $indexParamName = self::PARAM_PREFIX . count($params);
- $params[$indexParamName] = $index;
- $matchParamName = self::PARAM_PREFIX . count($params);
- $params[$matchParamName] = $match;
- if (!empty($options)) {
- $optionParts = [];
- foreach ($options as $name => $value) {
- if ($value instanceof Expression) {
- $actualValue = $value->expression;
- } else {
- $actualValue = self::PARAM_PREFIX . count($params);
- $params[$actualValue] = $value;
- }
- $optionParts[] = $actualValue . ' AS ' . $name;
- }
- $optionSql = ', ' . implode(', ', $optionParts);
- } else {
- $optionSql = '';
- }
- return 'CALL SNIPPETS(' . $dataSql. ', ' . $indexParamName . ', ' . $matchParamName . $optionSql. ')';
- }
-
- /**
- * Builds a SQL statement for returning tokenized and normalized forms of the keywords, and,
- * optionally, keyword statistics.
- * @param string $index the name of the index from which to take the text processing settings
- * @param string $text the text to break down to keywords.
- * @param boolean $fetchStatistic whether to return document and hit occurrence statistics
- * @param array $params the binding parameters that will be modified by this method
- * so that they can be bound to the Sphinx command later.
- * @return string the SQL statement for call keywords.
- */
- public function callKeywords($index, $text, $fetchStatistic, &$params)
- {
- $indexParamName = self::PARAM_PREFIX . count($params);
- $params[$indexParamName] = $index;
- $textParamName = self::PARAM_PREFIX . count($params);
- $params[$textParamName] = $text;
- return 'CALL KEYWORDS(' . $textParamName . ', ' . $indexParamName . ($fetchStatistic ? ', 1' : '') . ')';
- }
-
- /**
- * @param array $columns
- * @param array $params the binding parameters to be populated
- * @param boolean $distinct
- * @param string $selectOption
- * @return string the SELECT clause built from [[query]].
- */
- public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
- {
- $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
- if ($selectOption !== null) {
- $select .= ' ' . $selectOption;
- }
-
- if (empty($columns)) {
- return $select . ' *';
- }
-
- foreach ($columns as $i => $column) {
- if ($column instanceof Expression) {
- $columns[$i] = $column->expression;
- $params = array_merge($params, $column->params);
- } elseif (is_string($i)) {
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
- $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
- } elseif (strpos($column, '(') === false) {
- if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
- $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
- } else {
- $columns[$i] = $this->db->quoteColumnName($column);
- }
- }
- }
-
- return $select . ' ' . implode(', ', $columns);
- }
-
- /**
- * @param array $indexes
- * @param array $params the binding parameters to be populated
- * @return string the FROM clause built from [[query]].
- */
- public function buildFrom($indexes, &$params)
- {
- if (empty($indexes)) {
- return '';
- }
-
- foreach ($indexes as $i => $index) {
- if ($index instanceof Query) {
- list($sql, $params) = $this->build($index, $params);
- $indexes[$i] = "($sql) " . $this->db->quoteIndexName($i);
- } elseif (is_string($i)) {
- if (strpos($index, '(') === false) {
- $index = $this->db->quoteIndexName($index);
- }
- $indexes[$i] = "$index " . $this->db->quoteIndexName($i);
- } elseif (strpos($index, '(') === false) {
- if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $index, $matches)) { // with alias
- $indexes[$i] = $this->db->quoteIndexName($matches[1]) . ' ' . $this->db->quoteIndexName($matches[2]);
- } else {
- $indexes[$i] = $this->db->quoteIndexName($index);
- }
- }
- }
-
- if (is_array($indexes)) {
- $indexes = implode(', ', $indexes);
- }
-
- return 'FROM ' . $indexes;
- }
-
- /**
- * @param string[] $indexes list of index names, which affected by query
- * @param string|array $condition
- * @param array $params the binding parameters to be populated
- * @return string the WHERE clause built from [[query]].
- */
- public function buildWhere($indexes, $condition, &$params)
- {
- if (empty($condition)) {
- return '';
- }
- $indexSchemas = [];
- if (!empty($indexes)) {
- foreach ($indexes as $indexName) {
- $index = $this->db->getIndexSchema($indexName);
- if ($index !== null) {
- $indexSchemas[] = $index;
- }
- }
- }
- $where = $this->buildCondition($indexSchemas, $condition, $params);
- return $where === '' ? '' : 'WHERE ' . $where;
- }
-
- /**
- * @param array $columns
- * @return string the GROUP BY clause
- */
- public function buildGroupBy($columns)
- {
- return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
- }
-
- /**
- * @param array $columns
- * @return string the ORDER BY clause built from [[query]].
- */
- public function buildOrderBy($columns)
- {
- if (empty($columns)) {
- return '';
- }
- $orders = [];
- foreach ($columns as $name => $direction) {
- if ($direction instanceof Expression) {
- $orders[] = $direction->expression;
- } else {
- $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : 'ASC');
- }
- }
-
- return 'ORDER BY ' . implode(', ', $orders);
- }
-
- /**
- * @param integer $limit
- * @param integer $offset
- * @return string the LIMIT and OFFSET clauses built from [[query]].
- */
- public function buildLimit($limit, $offset)
- {
- $sql = '';
- if (is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0') {
- $sql = 'LIMIT ' . $offset;
- }
- if (is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0) {
- $sql = $sql === '' ? "LIMIT $limit" : "$sql,$limit";
- } elseif ($sql !== '') {
- $sql .= ',1000'; // this is the default limit by sphinx
- }
-
- return $sql;
- }
-
- /**
- * Processes columns and properly quote them if necessary.
- * It will join all columns into a string with comma as separators.
- * @param string|array $columns the columns to be processed
- * @return string the processing result
- */
- public function buildColumns($columns)
- {
- if (!is_array($columns)) {
- if (strpos($columns, '(') !== false) {
- return $columns;
- } else {
- $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
- }
- }
- foreach ($columns as $i => $column) {
- if ($column instanceof Expression) {
- $columns[$i] = $column->expression;
- } elseif (strpos($column, '(') === false) {
- $columns[$i] = $this->db->quoteColumnName($column);
- }
- }
- return is_array($columns) ? implode(', ', $columns) : $columns;
- }
-
- /**
- * Parses the condition specification and generates the corresponding SQL expression.
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
- * on how to specify a condition.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws \yii\db\Exception if the condition is in bad format
- */
- public function buildCondition($indexes, $condition, &$params)
- {
- static $builders = [
- 'AND' => 'buildAndCondition',
- 'OR' => 'buildAndCondition',
- 'BETWEEN' => 'buildBetweenCondition',
- 'NOT BETWEEN' => 'buildBetweenCondition',
- 'IN' => 'buildInCondition',
- 'NOT IN' => 'buildInCondition',
- 'LIKE' => 'buildLikeCondition',
- 'NOT LIKE' => 'buildLikeCondition',
- 'OR LIKE' => 'buildLikeCondition',
- 'OR NOT LIKE' => 'buildLikeCondition',
- ];
-
- if (!is_array($condition)) {
- return (string)$condition;
- } elseif (empty($condition)) {
- return '';
- }
- if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
- $operator = strtoupper($condition[0]);
- if (isset($builders[$operator])) {
- $method = $builders[$operator];
- array_shift($condition);
- return $this->$method($indexes, $operator, $condition, $params);
- } else {
- throw new Exception('Found unknown operator in query: ' . $operator);
- }
- } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
- return $this->buildHashCondition($indexes, $condition, $params);
- }
- }
-
- /**
- * Creates a condition based on column-value pairs.
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param array $condition the condition specification.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- */
- public function buildHashCondition($indexes, $condition, &$params)
- {
- $parts = [];
- foreach ($condition as $column => $value) {
- if (is_array($value)) { // IN condition
- $parts[] = $this->buildInCondition($indexes, 'IN', [$column, $value], $params);
- } else {
- if (strpos($column, '(') === false) {
- $quotedColumn = $this->db->quoteColumnName($column);
- } else {
- $quotedColumn = $column;
- }
- if ($value === null) {
- $parts[] = "$quotedColumn IS NULL";
- } else {
- $parts[] = $quotedColumn . '=' . $this->composeColumnValue($indexes, $column, $value, $params);
- }
- }
- }
- return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
- }
-
- /**
- * Connects two or more SQL expressions with the `AND` or `OR` operator.
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param string $operator the operator to use for connecting the given operands
- * @param array $operands the SQL expressions to connect.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- */
- public function buildAndCondition($indexes, $operator, $operands, &$params)
- {
- $parts = [];
- foreach ($operands as $operand) {
- if (is_array($operand)) {
- $operand = $this->buildCondition($indexes, $operand, $params);
- }
- if ($operand !== '') {
- $parts[] = $operand;
- }
- }
- if (!empty($parts)) {
- return '(' . implode(") $operator (", $parts) . ')';
- } else {
- return '';
- }
- }
-
- /**
- * Creates an SQL expressions with the `BETWEEN` operator.
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
- * @param array $operands the first operand is the column name. The second and third operands
- * describe the interval that column value should be in.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws Exception if wrong number of operands have been given.
- */
- public function buildBetweenCondition($indexes, $operator, $operands, &$params)
- {
- if (!isset($operands[0], $operands[1], $operands[2])) {
- throw new Exception("Operator '$operator' requires three operands.");
- }
-
- list($column, $value1, $value2) = $operands;
-
- if (strpos($column, '(') === false) {
- $quotedColumn = $this->db->quoteColumnName($column);
- } else {
- $quotedColumn = $column;
- }
- $phName1 = $this->composeColumnValue($indexes, $column, $value1, $params);
- $phName2 = $this->composeColumnValue($indexes, $column, $value2, $params);
-
- return "$quotedColumn $operator $phName1 AND $phName2";
- }
-
- /**
- * Creates an SQL expressions with the `IN` operator.
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
- * @param array $operands the first operand is the column name. If it is an array
- * a composite IN condition will be generated.
- * The second operand is an array of values that column value should be among.
- * If it is an empty array the generated expression will be a `false` value if
- * operator is `IN` and empty if operator is `NOT IN`.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws Exception if wrong number of operands have been given.
- */
- public function buildInCondition($indexes, $operator, $operands, &$params)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new Exception("Operator '$operator' requires two operands.");
- }
-
- list($column, $values) = $operands;
-
- $values = (array)$values;
-
- if (empty($values) || $column === []) {
- return $operator === 'IN' ? '0=1' : '';
- }
-
- if (count($column) > 1) {
- return $this->buildCompositeInCondition($indexes, $operator, $column, $values, $params);
- } elseif (is_array($column)) {
- $column = reset($column);
- }
- foreach ($values as $i => $value) {
- if (is_array($value)) {
- $value = isset($value[$column]) ? $value[$column] : null;
- }
- $values[$i] = $this->composeColumnValue($indexes, $column, $value, $params);
- }
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
-
- if (count($values) > 1) {
- return "$column $operator (" . implode(', ', $values) . ')';
- } else {
- $operator = $operator === 'IN' ? '=' : '<>';
- return $column . $operator . reset($values);
- }
- }
-
- /**
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
- * @param array $columns
- * @param array $values
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- */
- protected function buildCompositeInCondition($indexes, $operator, $columns, $values, &$params)
- {
- $vss = [];
- foreach ($values as $value) {
- $vs = [];
- foreach ($columns as $column) {
- if (isset($value[$column])) {
- $vs[] = $this->composeColumnValue($indexes, $column, $value[$column], $params);
- } else {
- $vs[] = 'NULL';
- }
- }
- $vss[] = '(' . implode(', ', $vs) . ')';
- }
- foreach ($columns as $i => $column) {
- if (strpos($column, '(') === false) {
- $columns[$i] = $this->db->quoteColumnName($column);
- }
- }
- return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
- }
-
- /**
- * Creates an SQL expressions with the `LIKE` operator.
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
- * @param array $operands an array of two or three operands
- *
- * - The first operand is the column name.
- * - The second operand is a single value or an array of values that column value
- * should be compared with. If it is an empty array the generated expression will
- * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
- * is `NOT LIKE` or `OR NOT LIKE`.
- * - An optional third operand can also be provided to specify how to escape special characters
- * in the value(s). The operand should be an array of mappings from the special characters to their
- * escaped counterparts. If this operand is not provided, a default escape mapping will be used.
- * You may use `false` or an empty array to indicate the values are already escaped and no escape
- * should be applied. Note that when using an escape mapping (or the third operand is not provided),
- * the values will be automatically enclosed within a pair of percentage characters.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws InvalidParamException if wrong number of operands have been given.
- */
- public function buildLikeCondition($indexes, $operator, $operands, &$params)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new InvalidParamException("Operator '$operator' requires two operands.");
- }
-
- $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
- unset($operands[2]);
-
- list($column, $values) = $operands;
-
- $values = (array)$values;
-
- if (empty($values)) {
- return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
- }
-
- if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
- $andor = ' AND ';
- } else {
- $andor = ' OR ';
- $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
- }
-
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
-
- $parts = [];
- foreach ($values as $value) {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
- $parts[] = "$column $operator $phName";
- }
-
- return implode($andor, $parts);
- }
-
- /**
- * @param array $columns
- * @return string the ORDER BY clause built from [[query]].
- */
- public function buildWithin($columns)
- {
- if (empty($columns)) {
- return '';
- }
- $orders = [];
- foreach ($columns as $name => $direction) {
- if ($direction instanceof Expression) {
- $orders[] = $direction->expression;
- } else {
- $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
- }
- }
- return 'WITHIN GROUP ORDER BY ' . implode(', ', $orders);
- }
-
- /**
- * @param array $options query options in format: optionName => optionValue
- * @param array $params the binding parameters to be populated
- * @return string the OPTION clause build from [[query]]
- */
- public function buildOption($options, &$params)
- {
- if (empty($options)) {
- return '';
- }
- $optionLines = [];
- foreach ($options as $name => $value) {
- if ($value instanceof Expression) {
- $actualValue = $value->expression;
- } else {
- if (is_array($value)) {
- $actualValueParts = [];
- foreach ($value as $key => $valuePart) {
- if (is_numeric($key)) {
- $actualValuePart = '';
- } else {
- $actualValuePart = $key . ' = ';
- }
- if ($valuePart instanceof Expression) {
- $actualValuePart .= $valuePart->expression;
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = $valuePart;
- $actualValuePart .= $phName;
- }
- $actualValueParts[] = $actualValuePart;
- }
- $actualValue = '(' . implode(', ', $actualValueParts) . ')';
- } else {
- $actualValue = self::PARAM_PREFIX . count($params);
- $params[$actualValue] = $value;
- }
- }
- $optionLines[] = $name . ' = ' . $actualValue;
- }
- return 'OPTION ' . implode(', ', $optionLines);
- }
-
- /**
- * Composes column value for SQL, taking in account the column type.
- * @param IndexSchema[] $indexes list of indexes, which affected by query
- * @param string $columnName name of the column
- * @param mixed $value raw column value
- * @param array $params the binding parameters to be populated
- * @return string SQL expression, which represents column value
- */
- protected function composeColumnValue($indexes, $columnName, $value, &$params)
- {
- if ($value === null) {
- return 'NULL';
- } elseif ($value instanceof Expression) {
- $params = array_merge($params, $value->params);
- return $value->expression;
- }
- foreach ($indexes as $index) {
- $columnSchema = $index->getColumn($columnName);
- if ($columnSchema !== null) {
- break;
- }
- }
- if (is_array($value)) {
- // MVA :
- $lineParts = [];
- foreach ($value as $subValue) {
- if ($subValue instanceof Expression) {
- $params = array_merge($params, $subValue->params);
- $lineParts[] = $subValue->expression;
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $lineParts[] = $phName;
- $params[$phName] = (isset($columnSchema)) ? $columnSchema->typecast($subValue) : $subValue;
- }
- }
- return '(' . implode(',', $lineParts) . ')';
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = (isset($columnSchema)) ? $columnSchema->typecast($value) : $value;
- return $phName;
- }
- }
+ /**
+ * The prefix for automatically generated query binding parameters.
+ */
+ const PARAM_PREFIX = ':qp';
+
+ /**
+ * @var Connection the Sphinx connection.
+ */
+ public $db;
+ /**
+ * @var string the separator between different fragments of a SQL statement.
+ * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
+ */
+ public $separator = " ";
+
+ /**
+ * Constructor.
+ * @param Connection $connection the Sphinx connection.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($connection, $config = [])
+ {
+ $this->db = $connection;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates a SELECT SQL statement from a [[Query]] object.
+ * @param Query $query the [[Query]] object from which the SQL statement will be generated
+ * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
+ * be included in the result with the additional parameters generated during the query building process.
+ * @return array the generated SQL statement (the first array element) and the corresponding
+ * parameters to be bound to the SQL statement (the second array element). The parameters returned
+ * include those provided in `$params`.
+ */
+ public function build($query, $params = [])
+ {
+ $params = empty($params) ? $query->params : array_merge($params, $query->params);
+
+ if ($query->match !== null) {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = (string) $query->match;
+ $query->andWhere('MATCH(' . $phName . ')');
+ }
+
+ $from = $query->from;
+ if ($from === null && $query instanceof ActiveQuery) {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $from = [$modelClass::indexName()];
+ }
+
+ $clauses = [
+ $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
+ $this->buildFrom($from, $params),
+ $this->buildWhere($query->from, $query->where, $params),
+ $this->buildGroupBy($query->groupBy),
+ $this->buildWithin($query->within),
+ $this->buildOrderBy($query->orderBy),
+ $this->buildLimit($query->limit, $query->offset),
+ $this->buildOption($query->options, $params),
+ ];
+
+ return [implode($this->separator, array_filter($clauses)), $params];
+ }
+
+ /**
+ * Creates an INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->insert('idx_user', [
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * 'id' => 10,
+ * ], $params);
+ * ~~~
+ *
+ * The method will properly escape the index and column names.
+ *
+ * @param string $index the index that new rows will be inserted into.
+ * @param array $columns the column data (name => value) to be inserted into the index.
+ * @param array $params the binding parameters that will be generated by this method.
+ * They should be bound to the Sphinx command later.
+ * @return string the INSERT SQL
+ */
+ public function insert($index, $columns, &$params)
+ {
+ return $this->generateInsertReplace('INSERT', $index, $columns, $params);
+ }
+
+ /**
+ * Creates an REPLACE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->replace('idx_user', [
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * 'id' => 10,
+ * ], $params);
+ * ~~~
+ *
+ * The method will properly escape the index and column names.
+ *
+ * @param string $index the index that new rows will be replaced.
+ * @param array $columns the column data (name => value) to be replaced in the index.
+ * @param array $params the binding parameters that will be generated by this method.
+ * They should be bound to the Sphinx command later.
+ * @return string the INSERT SQL
+ */
+ public function replace($index, $columns, &$params)
+ {
+ return $this->generateInsertReplace('REPLACE', $index, $columns, $params);
+ }
+
+ /**
+ * Generates INSERT/REPLACE SQL statement.
+ * @param string $statement statement ot be generated.
+ * @param string $index the affected index name.
+ * @param array $columns the column data (name => value).
+ * @param array $params the binding parameters that will be generated by this method.
+ * @return string generated SQL
+ */
+ protected function generateInsertReplace($statement, $index, $columns, &$params)
+ {
+ if (($indexSchema = $this->db->getIndexSchema($index)) !== null) {
+ $indexSchemas = [$indexSchema];
+ } else {
+ $indexSchemas = [];
+ }
+ $names = [];
+ $placeholders = [];
+ foreach ($columns as $name => $value) {
+ $names[] = $this->db->quoteColumnName($name);
+ $placeholders[] = $this->composeColumnValue($indexSchemas, $name, $value, $params);
+ }
+
+ return $statement . ' INTO ' . $this->db->quoteIndexName($index)
+ . ' (' . implode(', ', $names) . ') VALUES ('
+ . implode(', ', $placeholders) . ')';
+ }
+
+ /**
+ * Generates a batch INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->batchInsert('idx_user', ['id', 'name', 'age'], [
+ * [1, 'Tom', 30],
+ * [2, 'Jane', 20],
+ * [3, 'Linda', 25],
+ * ], $params);
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $index the index that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the index
+ * @param array $params the binding parameters that will be generated by this method.
+ * They should be bound to the Sphinx command later.
+ * @return string the batch INSERT SQL statement
+ */
+ public function batchInsert($index, $columns, $rows, &$params)
+ {
+ return $this->generateBatchInsertReplace('INSERT', $index, $columns, $rows, $params);
+ }
+
+ /**
+ * Generates a batch REPLACE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->batchReplace('idx_user', ['id', 'name', 'age'], [
+ * [1, 'Tom', 30],
+ * [2, 'Jane', 20],
+ * [3, 'Linda', 25],
+ * ], $params);
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $index the index that new rows will be replaced.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch replaced in the index
+ * @param array $params the binding parameters that will be generated by this method.
+ * They should be bound to the Sphinx command later.
+ * @return string the batch INSERT SQL statement
+ */
+ public function batchReplace($index, $columns, $rows, &$params)
+ {
+ return $this->generateBatchInsertReplace('REPLACE', $index, $columns, $rows, $params);
+ }
+
+ /**
+ * Generates a batch INSERT/REPLACE SQL statement.
+ * @param string $statement statement ot be generated.
+ * @param string $index the affected index name.
+ * @param array $columns the column data (name => value).
+ * @param array $rows the rows to be batch inserted into the index
+ * @param array $params the binding parameters that will be generated by this method.
+ * @return string generated SQL
+ */
+ protected function generateBatchInsertReplace($statement, $index, $columns, $rows, &$params)
+ {
+ if (($indexSchema = $this->db->getIndexSchema($index)) !== null) {
+ $indexSchemas = [$indexSchema];
+ } else {
+ $indexSchemas = [];
+ }
+
+ foreach ($columns as $i => $name) {
+ $columns[$i] = $this->db->quoteColumnName($name);
+ }
+
+ $values = [];
+ foreach ($rows as $row) {
+ $vs = [];
+ foreach ($row as $i => $value) {
+ $vs[] = $this->composeColumnValue($indexSchemas, $columns[$i], $value, $params);
+ }
+ $values[] = '(' . implode(', ', $vs) . ')';
+ }
+
+ return $statement . ' INTO ' . $this->db->quoteIndexName($index)
+ . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
+ }
+
+ /**
+ * Creates an UPDATE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $params = [];
+ * $sql = $queryBuilder->update('idx_user', ['status' => 1], 'age > 30', $params);
+ * ~~~
+ *
+ * The method will properly escape the index and column names.
+ *
+ * @param string $index the index to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param array|string $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the Sphinx command later.
+ * @param array $options list of options in format: optionName => optionValue
+ * @return string the UPDATE SQL
+ */
+ public function update($index, $columns, $condition, &$params, $options)
+ {
+ if (($indexSchema = $this->db->getIndexSchema($index)) !== null) {
+ $indexSchemas = [$indexSchema];
+ } else {
+ $indexSchemas = [];
+ }
+
+ $lines = [];
+ foreach ($columns as $name => $value) {
+ $lines[] = $this->db->quoteColumnName($name) . '=' . $this->composeColumnValue($indexSchemas, $name, $value, $params);
+ }
+
+ $sql = 'UPDATE ' . $this->db->quoteIndexName($index) . ' SET ' . implode(', ', $lines);
+ $where = $this->buildWhere([$index], $condition, $params);
+ if ($where !== '') {
+ $sql = $sql . ' ' . $where;
+ }
+ $option = $this->buildOption($options, $params);
+ if ($option !== '') {
+ $sql = $sql . ' ' . $option;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Creates a DELETE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->delete('idx_user', 'status = 0');
+ * ~~~
+ *
+ * The method will properly escape the index and column names.
+ *
+ * @param string $index the index where the data will be deleted from.
+ * @param array|string $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the Sphinx command later.
+ * @return string the DELETE SQL
+ */
+ public function delete($index, $condition, &$params)
+ {
+ $sql = 'DELETE FROM ' . $this->db->quoteIndexName($index);
+ $where = $this->buildWhere([$index], $condition, $params);
+
+ return $where === '' ? $sql : $sql . ' ' . $where;
+ }
+
+ /**
+ * Builds a SQL statement for truncating an index.
+ * @param string $index the index to be truncated. The name will be properly quoted by the method.
+ * @return string the SQL statement for truncating an index.
+ */
+ public function truncateIndex($index)
+ {
+ return 'TRUNCATE RTINDEX ' . $this->db->quoteIndexName($index);
+ }
+
+ /**
+ * Builds a SQL statement for call snippet from provided data and query, using specified index settings.
+ * @param string $index name of the index, from which to take the text processing settings.
+ * @param string|array $source is the source data to extract a snippet from.
+ * It could be either a single string or array of strings.
+ * @param string $match the full-text query to build snippets for.
+ * @param array $options list of options in format: optionName => optionValue
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the Sphinx command later.
+ * @return string the SQL statement for call snippets.
+ */
+ public function callSnippets($index, $source, $match, $options, &$params)
+ {
+ if (is_array($source)) {
+ $dataSqlParts = [];
+ foreach ($source as $sourceRow) {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $sourceRow;
+ $dataSqlParts[] = $phName;
+ }
+ $dataSql = '(' . implode(',', $dataSqlParts) . ')';
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $source;
+ $dataSql = $phName;
+ }
+ $indexParamName = self::PARAM_PREFIX . count($params);
+ $params[$indexParamName] = $index;
+ $matchParamName = self::PARAM_PREFIX . count($params);
+ $params[$matchParamName] = $match;
+ if (!empty($options)) {
+ $optionParts = [];
+ foreach ($options as $name => $value) {
+ if ($value instanceof Expression) {
+ $actualValue = $value->expression;
+ } else {
+ $actualValue = self::PARAM_PREFIX . count($params);
+ $params[$actualValue] = $value;
+ }
+ $optionParts[] = $actualValue . ' AS ' . $name;
+ }
+ $optionSql = ', ' . implode(', ', $optionParts);
+ } else {
+ $optionSql = '';
+ }
+
+ return 'CALL SNIPPETS(' . $dataSql. ', ' . $indexParamName . ', ' . $matchParamName . $optionSql. ')';
+ }
+
+ /**
+ * Builds a SQL statement for returning tokenized and normalized forms of the keywords, and,
+ * optionally, keyword statistics.
+ * @param string $index the name of the index from which to take the text processing settings
+ * @param string $text the text to break down to keywords.
+ * @param boolean $fetchStatistic whether to return document and hit occurrence statistics
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the Sphinx command later.
+ * @return string the SQL statement for call keywords.
+ */
+ public function callKeywords($index, $text, $fetchStatistic, &$params)
+ {
+ $indexParamName = self::PARAM_PREFIX . count($params);
+ $params[$indexParamName] = $index;
+ $textParamName = self::PARAM_PREFIX . count($params);
+ $params[$textParamName] = $text;
+
+ return 'CALL KEYWORDS(' . $textParamName . ', ' . $indexParamName . ($fetchStatistic ? ', 1' : '') . ')';
+ }
+
+ /**
+ * @param array $columns
+ * @param array $params the binding parameters to be populated
+ * @param boolean $distinct
+ * @param string $selectOption
+ * @return string the SELECT clause built from [[query]].
+ */
+ public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
+ {
+ $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
+ if ($selectOption !== null) {
+ $select .= ' ' . $selectOption;
+ }
+
+ if (empty($columns)) {
+ return $select . ' *';
+ }
+
+ foreach ($columns as $i => $column) {
+ if ($column instanceof Expression) {
+ $columns[$i] = $column->expression;
+ $params = array_merge($params, $column->params);
+ } elseif (is_string($i)) {
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+ $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
+ } elseif (strpos($column, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
+ $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
+ } else {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+ }
+
+ return $select . ' ' . implode(', ', $columns);
+ }
+
+ /**
+ * @param array $indexes
+ * @param array $params the binding parameters to be populated
+ * @return string the FROM clause built from [[query]].
+ */
+ public function buildFrom($indexes, &$params)
+ {
+ if (empty($indexes)) {
+ return '';
+ }
+
+ foreach ($indexes as $i => $index) {
+ if ($index instanceof Query) {
+ list($sql, $params) = $this->build($index, $params);
+ $indexes[$i] = "($sql) " . $this->db->quoteIndexName($i);
+ } elseif (is_string($i)) {
+ if (strpos($index, '(') === false) {
+ $index = $this->db->quoteIndexName($index);
+ }
+ $indexes[$i] = "$index " . $this->db->quoteIndexName($i);
+ } elseif (strpos($index, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $index, $matches)) { // with alias
+ $indexes[$i] = $this->db->quoteIndexName($matches[1]) . ' ' . $this->db->quoteIndexName($matches[2]);
+ } else {
+ $indexes[$i] = $this->db->quoteIndexName($index);
+ }
+ }
+ }
+
+ if (is_array($indexes)) {
+ $indexes = implode(', ', $indexes);
+ }
+
+ return 'FROM ' . $indexes;
+ }
+
+ /**
+ * @param string[] $indexes list of index names, which affected by query
+ * @param string|array $condition
+ * @param array $params the binding parameters to be populated
+ * @return string the WHERE clause built from [[query]].
+ */
+ public function buildWhere($indexes, $condition, &$params)
+ {
+ if (empty($condition)) {
+ return '';
+ }
+ $indexSchemas = [];
+ if (!empty($indexes)) {
+ foreach ($indexes as $indexName) {
+ $index = $this->db->getIndexSchema($indexName);
+ if ($index !== null) {
+ $indexSchemas[] = $index;
+ }
+ }
+ }
+ $where = $this->buildCondition($indexSchemas, $condition, $params);
+
+ return $where === '' ? '' : 'WHERE ' . $where;
+ }
+
+ /**
+ * @param array $columns
+ * @return string the GROUP BY clause
+ */
+ public function buildGroupBy($columns)
+ {
+ return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
+ }
+
+ /**
+ * @param array $columns
+ * @return string the ORDER BY clause built from [[query]].
+ */
+ public function buildOrderBy($columns)
+ {
+ if (empty($columns)) {
+ return '';
+ }
+ $orders = [];
+ foreach ($columns as $name => $direction) {
+ if ($direction instanceof Expression) {
+ $orders[] = $direction->expression;
+ } else {
+ $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : 'ASC');
+ }
+ }
+
+ return 'ORDER BY ' . implode(', ', $orders);
+ }
+
+ /**
+ * @param integer $limit
+ * @param integer $offset
+ * @return string the LIMIT and OFFSET clauses built from [[query]].
+ */
+ public function buildLimit($limit, $offset)
+ {
+ $sql = '';
+ if (is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0') {
+ $sql = 'LIMIT ' . $offset;
+ }
+ if (is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0) {
+ $sql = $sql === '' ? "LIMIT $limit" : "$sql,$limit";
+ } elseif ($sql !== '') {
+ $sql .= ',1000'; // this is the default limit by sphinx
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Processes columns and properly quote them if necessary.
+ * It will join all columns into a string with comma as separators.
+ * @param string|array $columns the columns to be processed
+ * @return string the processing result
+ */
+ public function buildColumns($columns)
+ {
+ if (!is_array($columns)) {
+ if (strpos($columns, '(') !== false) {
+ return $columns;
+ } else {
+ $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
+ }
+ }
+ foreach ($columns as $i => $column) {
+ if ($column instanceof Expression) {
+ $columns[$i] = $column->expression;
+ } elseif (strpos($column, '(') === false) {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+
+ return is_array($columns) ? implode(', ', $columns) : $columns;
+ }
+
+ /**
+ * Parses the condition specification and generates the corresponding SQL expression.
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
+ * on how to specify a condition.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws \yii\db\Exception if the condition is in bad format
+ */
+ public function buildCondition($indexes, $condition, &$params)
+ {
+ static $builders = [
+ 'AND' => 'buildAndCondition',
+ 'OR' => 'buildAndCondition',
+ 'BETWEEN' => 'buildBetweenCondition',
+ 'NOT BETWEEN' => 'buildBetweenCondition',
+ 'IN' => 'buildInCondition',
+ 'NOT IN' => 'buildInCondition',
+ 'LIKE' => 'buildLikeCondition',
+ 'NOT LIKE' => 'buildLikeCondition',
+ 'OR LIKE' => 'buildLikeCondition',
+ 'OR NOT LIKE' => 'buildLikeCondition',
+ ];
+
+ if (!is_array($condition)) {
+ return (string) $condition;
+ } elseif (empty($condition)) {
+ return '';
+ }
+ if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
+ $operator = strtoupper($condition[0]);
+ if (isset($builders[$operator])) {
+ $method = $builders[$operator];
+ array_shift($condition);
+
+ return $this->$method($indexes, $operator, $condition, $params);
+ } else {
+ throw new Exception('Found unknown operator in query: ' . $operator);
+ }
+ } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
+
+ return $this->buildHashCondition($indexes, $condition, $params);
+ }
+ }
+
+ /**
+ * Creates a condition based on column-value pairs.
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param array $condition the condition specification.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ */
+ public function buildHashCondition($indexes, $condition, &$params)
+ {
+ $parts = [];
+ foreach ($condition as $column => $value) {
+ if (is_array($value)) { // IN condition
+ $parts[] = $this->buildInCondition($indexes, 'IN', [$column, $value], $params);
+ } else {
+ if (strpos($column, '(') === false) {
+ $quotedColumn = $this->db->quoteColumnName($column);
+ } else {
+ $quotedColumn = $column;
+ }
+ if ($value === null) {
+ $parts[] = "$quotedColumn IS NULL";
+ } else {
+ $parts[] = $quotedColumn . '=' . $this->composeColumnValue($indexes, $column, $value, $params);
+ }
+ }
+ }
+
+ return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
+ }
+
+ /**
+ * Connects two or more SQL expressions with the `AND` or `OR` operator.
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param string $operator the operator to use for connecting the given operands
+ * @param array $operands the SQL expressions to connect.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ */
+ public function buildAndCondition($indexes, $operator, $operands, &$params)
+ {
+ $parts = [];
+ foreach ($operands as $operand) {
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($indexes, $operand, $params);
+ }
+ if ($operand !== '') {
+ $parts[] = $operand;
+ }
+ }
+ if (!empty($parts)) {
+ return '(' . implode(") $operator (", $parts) . ')';
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Creates an SQL expressions with the `BETWEEN` operator.
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
+ * @param array $operands the first operand is the column name. The second and third operands
+ * describe the interval that column value should be in.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws Exception if wrong number of operands have been given.
+ */
+ public function buildBetweenCondition($indexes, $operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1], $operands[2])) {
+ throw new Exception("Operator '$operator' requires three operands.");
+ }
+
+ list($column, $value1, $value2) = $operands;
+
+ if (strpos($column, '(') === false) {
+ $quotedColumn = $this->db->quoteColumnName($column);
+ } else {
+ $quotedColumn = $column;
+ }
+ $phName1 = $this->composeColumnValue($indexes, $column, $value1, $params);
+ $phName2 = $this->composeColumnValue($indexes, $column, $value2, $params);
+
+ return "$quotedColumn $operator $phName1 AND $phName2";
+ }
+
+ /**
+ * Creates an SQL expressions with the `IN` operator.
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
+ * @param array $operands the first operand is the column name. If it is an array
+ * a composite IN condition will be generated.
+ * The second operand is an array of values that column value should be among.
+ * If it is an empty array the generated expression will be a `false` value if
+ * operator is `IN` and empty if operator is `NOT IN`.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws Exception if wrong number of operands have been given.
+ */
+ public function buildInCondition($indexes, $operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new Exception("Operator '$operator' requires two operands.");
+ }
+
+ list($column, $values) = $operands;
+
+ $values = (array) $values;
+
+ if (empty($values) || $column === []) {
+ return $operator === 'IN' ? '0=1' : '';
+ }
+
+ if (count($column) > 1) {
+ return $this->buildCompositeInCondition($indexes, $operator, $column, $values, $params);
+ } elseif (is_array($column)) {
+ $column = reset($column);
+ }
+ foreach ($values as $i => $value) {
+ if (is_array($value)) {
+ $value = isset($value[$column]) ? $value[$column] : null;
+ }
+ $values[$i] = $this->composeColumnValue($indexes, $column, $value, $params);
+ }
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+
+ if (count($values) > 1) {
+ return "$column $operator (" . implode(', ', $values) . ')';
+ } else {
+ $operator = $operator === 'IN' ? '=' : '<>';
+
+ return $column . $operator . reset($values);
+ }
+ }
+
+ /**
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
+ * @param array $columns
+ * @param array $values
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ */
+ protected function buildCompositeInCondition($indexes, $operator, $columns, $values, &$params)
+ {
+ $vss = [];
+ foreach ($values as $value) {
+ $vs = [];
+ foreach ($columns as $column) {
+ if (isset($value[$column])) {
+ $vs[] = $this->composeColumnValue($indexes, $column, $value[$column], $params);
+ } else {
+ $vs[] = 'NULL';
+ }
+ }
+ $vss[] = '(' . implode(', ', $vs) . ')';
+ }
+ foreach ($columns as $i => $column) {
+ if (strpos($column, '(') === false) {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+
+ return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
+ }
+
+ /**
+ * Creates an SQL expressions with the `LIKE` operator.
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
+ * @param array $operands an array of two or three operands
+ *
+ * - The first operand is the column name.
+ * - The second operand is a single value or an array of values that column value
+ * should be compared with. If it is an empty array the generated expression will
+ * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
+ * is `NOT LIKE` or `OR NOT LIKE`.
+ * - An optional third operand can also be provided to specify how to escape special characters
+ * in the value(s). The operand should be an array of mappings from the special characters to their
+ * escaped counterparts. If this operand is not provided, a default escape mapping will be used.
+ * You may use `false` or an empty array to indicate the values are already escaped and no escape
+ * should be applied. Note that when using an escape mapping (or the third operand is not provided),
+ * the values will be automatically enclosed within a pair of percentage characters.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws InvalidParamException if wrong number of operands have been given.
+ */
+ public function buildLikeCondition($indexes, $operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new InvalidParamException("Operator '$operator' requires two operands.");
+ }
+
+ $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
+ unset($operands[2]);
+
+ list($column, $values) = $operands;
+
+ $values = (array) $values;
+
+ if (empty($values)) {
+ return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
+ }
+
+ if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
+ $andor = ' AND ';
+ } else {
+ $andor = ' OR ';
+ $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
+ }
+
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+
+ $parts = [];
+ foreach ($values as $value) {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
+ $parts[] = "$column $operator $phName";
+ }
+
+ return implode($andor, $parts);
+ }
+
+ /**
+ * @param array $columns
+ * @return string the ORDER BY clause built from [[query]].
+ */
+ public function buildWithin($columns)
+ {
+ if (empty($columns)) {
+ return '';
+ }
+ $orders = [];
+ foreach ($columns as $name => $direction) {
+ if ($direction instanceof Expression) {
+ $orders[] = $direction->expression;
+ } else {
+ $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
+ }
+ }
+
+ return 'WITHIN GROUP ORDER BY ' . implode(', ', $orders);
+ }
+
+ /**
+ * @param array $options query options in format: optionName => optionValue
+ * @param array $params the binding parameters to be populated
+ * @return string the OPTION clause build from [[query]]
+ */
+ public function buildOption($options, &$params)
+ {
+ if (empty($options)) {
+ return '';
+ }
+ $optionLines = [];
+ foreach ($options as $name => $value) {
+ if ($value instanceof Expression) {
+ $actualValue = $value->expression;
+ } else {
+ if (is_array($value)) {
+ $actualValueParts = [];
+ foreach ($value as $key => $valuePart) {
+ if (is_numeric($key)) {
+ $actualValuePart = '';
+ } else {
+ $actualValuePart = $key . ' = ';
+ }
+ if ($valuePart instanceof Expression) {
+ $actualValuePart .= $valuePart->expression;
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $valuePart;
+ $actualValuePart .= $phName;
+ }
+ $actualValueParts[] = $actualValuePart;
+ }
+ $actualValue = '(' . implode(', ', $actualValueParts) . ')';
+ } else {
+ $actualValue = self::PARAM_PREFIX . count($params);
+ $params[$actualValue] = $value;
+ }
+ }
+ $optionLines[] = $name . ' = ' . $actualValue;
+ }
+
+ return 'OPTION ' . implode(', ', $optionLines);
+ }
+
+ /**
+ * Composes column value for SQL, taking in account the column type.
+ * @param IndexSchema[] $indexes list of indexes, which affected by query
+ * @param string $columnName name of the column
+ * @param mixed $value raw column value
+ * @param array $params the binding parameters to be populated
+ * @return string SQL expression, which represents column value
+ */
+ protected function composeColumnValue($indexes, $columnName, $value, &$params)
+ {
+ if ($value === null) {
+ return 'NULL';
+ } elseif ($value instanceof Expression) {
+ $params = array_merge($params, $value->params);
+
+ return $value->expression;
+ }
+ foreach ($indexes as $index) {
+ $columnSchema = $index->getColumn($columnName);
+ if ($columnSchema !== null) {
+ break;
+ }
+ }
+ if (is_array($value)) {
+ // MVA :
+ $lineParts = [];
+ foreach ($value as $subValue) {
+ if ($subValue instanceof Expression) {
+ $params = array_merge($params, $subValue->params);
+ $lineParts[] = $subValue->expression;
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $lineParts[] = $phName;
+ $params[$phName] = (isset($columnSchema)) ? $columnSchema->typecast($subValue) : $subValue;
+ }
+ }
+
+ return '(' . implode(',', $lineParts) . ')';
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = (isset($columnSchema)) ? $columnSchema->typecast($value) : $value;
+
+ return $phName;
+ }
+ }
}
diff --git a/extensions/sphinx/Schema.php b/extensions/sphinx/Schema.php
index 81a7a57e383..f1b20fa61d5 100644
--- a/extensions/sphinx/Schema.php
+++ b/extensions/sphinx/Schema.php
@@ -27,463 +27,476 @@
*/
class Schema extends Object
{
- /**
- * The followings are the supported abstract column data types.
- */
- const TYPE_PK = 'pk';
- const TYPE_STRING = 'string';
- const TYPE_INTEGER = 'integer';
- const TYPE_BIGINT = 'bigint';
- const TYPE_FLOAT = 'float';
- const TYPE_TIMESTAMP = 'timestamp';
- const TYPE_BOOLEAN = 'boolean';
-
- /**
- * @var Connection the Sphinx connection
- */
- public $db;
- /**
- * @var array list of ALL index names in the Sphinx
- */
- private $_indexNames;
- /**
- * @var array list of ALL index types in the Sphinx (index name => index type)
- */
- private $_indexTypes;
- /**
- * @var array list of loaded index metadata (index name => IndexSchema)
- */
- private $_indexes = [];
- /**
- * @var QueryBuilder the query builder for this Sphinx connection
- */
- private $_builder;
-
- /**
- * @var array mapping from physical column types (keys) to abstract column types (values)
- */
- public $typeMap = [
- 'field' => self::TYPE_STRING,
- 'string' => self::TYPE_STRING,
- 'ordinal' => self::TYPE_STRING,
- 'integer' => self::TYPE_INTEGER,
- 'int' => self::TYPE_INTEGER,
- 'uint' => self::TYPE_INTEGER,
- 'bigint' => self::TYPE_BIGINT,
- 'timestamp' => self::TYPE_TIMESTAMP,
- 'bool' => self::TYPE_BOOLEAN,
- 'float' => self::TYPE_FLOAT,
- 'mva' => self::TYPE_INTEGER,
- ];
-
- /**
- * Loads the metadata for the specified index.
- * @param string $name index name
- * @return IndexSchema driver dependent index metadata. Null if the index does not exist.
- */
- protected function loadIndexSchema($name)
- {
- $index = new IndexSchema;
- $this->resolveIndexNames($index, $name);
- $this->resolveIndexType($index);
-
- if ($this->findColumns($index)) {
- return $index;
- } else {
- return null;
- }
- }
-
- /**
- * Resolves the index name.
- * @param IndexSchema $index the index metadata object
- * @param string $name the index name
- */
- protected function resolveIndexNames($index, $name)
- {
- $index->name = str_replace('`', '', $name);
- }
-
- /**
- * Resolves the index name.
- * @param IndexSchema $index the index metadata object
- */
- protected function resolveIndexType($index)
- {
- $indexTypes = $this->getIndexTypes();
- $index->type = array_key_exists($index->name, $indexTypes) ? $indexTypes[$index->name] : 'unknown';
- $index->isRuntime = ($index->type == 'rt');
- }
-
- /**
- * Obtains the metadata for the named index.
- * @param string $name index name. The index name may contain schema name if any. Do not quote the index name.
- * @param boolean $refresh whether to reload the index schema even if it is found in the cache.
- * @return IndexSchema index metadata. Null if the named index does not exist.
- */
- public function getIndexSchema($name, $refresh = false)
- {
- if (isset($this->_indexes[$name]) && !$refresh) {
- return $this->_indexes[$name];
- }
-
- $db = $this->db;
- $realName = $this->getRawIndexName($name);
-
- if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
- /** @var $cache Cache */
- $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
- if ($cache instanceof Cache) {
- $key = $this->getCacheKey($name);
- if ($refresh || ($index = $cache->get($key)) === false) {
- $index = $this->loadIndexSchema($realName);
- if ($index !== null) {
- $cache->set($key, $index, $db->schemaCacheDuration, new GroupDependency([
- 'group' => $this->getCacheGroup(),
- ]));
- }
- }
- return $this->_indexes[$name] = $index;
- }
- }
- return $this->_indexes[$name] = $this->loadIndexSchema($realName);
- }
-
- /**
- * Returns the cache key for the specified index name.
- * @param string $name the index name
- * @return mixed the cache key
- */
- protected function getCacheKey($name)
- {
- return [
- __CLASS__,
- $this->db->dsn,
- $this->db->username,
- $name,
- ];
- }
-
- /**
- * Returns the cache group name.
- * This allows [[refresh()]] to invalidate all cached index schemas.
- * @return string the cache group name
- */
- protected function getCacheGroup()
- {
- return md5(serialize([
- __CLASS__,
- $this->db->dsn,
- $this->db->username,
- ]));
- }
-
- /**
- * Returns the metadata for all indexes in the database.
- * @param boolean $refresh whether to fetch the latest available index schemas. If this is false,
- * cached data may be returned if available.
- * @return IndexSchema[] the metadata for all indexes in the Sphinx.
- * Each array element is an instance of [[IndexSchema]] or its child class.
- */
- public function getIndexSchemas($refresh = false)
- {
- $indexes = [];
- foreach ($this->getIndexNames($refresh) as $name) {
- if (($index = $this->getIndexSchema($name, $refresh)) !== null) {
- $indexes[] = $index;
- }
- }
- return $indexes;
- }
-
- /**
- * Returns all index names in the Sphinx.
- * @param boolean $refresh whether to fetch the latest available index names. If this is false,
- * index names fetched previously (if available) will be returned.
- * @return string[] all index names in the Sphinx.
- */
- public function getIndexNames($refresh = false)
- {
- if (!isset($this->_indexNames) || $refresh) {
- $this->initIndexesInfo();
- }
- return $this->_indexNames;
- }
-
- /**
- * Returns all index types in the Sphinx.
- * @param boolean $refresh whether to fetch the latest available index types. If this is false,
- * index types fetched previously (if available) will be returned.
- * @return array all index types in the Sphinx in format: index name => index type.
- */
- public function getIndexTypes($refresh = false)
- {
- if (!isset($this->_indexTypes) || $refresh) {
- $this->initIndexesInfo();
- }
- return $this->_indexTypes;
- }
-
- /**
- * Initializes information about name and type of all index in the Sphinx.
- */
- protected function initIndexesInfo()
- {
- $this->_indexNames = [];
- $this->_indexTypes = [];
- $indexes = $this->findIndexes();
- foreach ($indexes as $index) {
- $indexName = $index['Index'];
- $this->_indexNames[] = $indexName;
- $this->_indexTypes[$indexName] = $index['Type'];
- }
- }
-
- /**
- * Returns all index names in the Sphinx.
- * @return array all index names in the Sphinx.
- */
- protected function findIndexes()
- {
- $sql = 'SHOW TABLES';
- return $this->db->createCommand($sql)->queryAll();
- }
-
- /**
- * @return QueryBuilder the query builder for this connection.
- */
- public function getQueryBuilder()
- {
- if ($this->_builder === null) {
- $this->_builder = $this->createQueryBuilder();
- }
- return $this->_builder;
- }
-
- /**
- * Determines the PDO type for the given PHP data value.
- * @param mixed $data the data whose PDO type is to be determined
- * @return integer the PDO type
- * @see http://www.php.net/manual/en/pdo.constants.php
- */
- public function getPdoType($data)
- {
- static $typeMap = [
- // php type => PDO type
- 'boolean' => \PDO::PARAM_BOOL,
- 'integer' => \PDO::PARAM_INT,
- 'string' => \PDO::PARAM_STR,
- 'resource' => \PDO::PARAM_LOB,
- 'NULL' => \PDO::PARAM_NULL,
- ];
- $type = gettype($data);
- return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
- }
-
- /**
- * Refreshes the schema.
- * This method cleans up all cached index schemas so that they can be re-created later
- * to reflect the Sphinx schema change.
- */
- public function refresh()
- {
- /** @var $cache Cache */
- $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
- if ($this->db->enableSchemaCache && $cache instanceof Cache) {
- GroupDependency::invalidate($cache, $this->getCacheGroup());
- }
- $this->_indexNames = [];
- $this->_indexes = [];
- }
-
- /**
- * Creates a query builder for the Sphinx.
- * @return QueryBuilder query builder instance
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
-
- /**
- * Quotes a string value for use in a query.
- * Note that if the parameter is not a string, it will be returned without change.
- * @param string $str string to be quoted
- * @return string the properly quoted string
- * @see http://www.php.net/manual/en/function.PDO-quote.php
- */
- public function quoteValue($str)
- {
- if (!is_string($str)) {
- return $str;
- }
- $this->db->open();
- return $this->db->pdo->quote($str);
- }
-
- /**
- * Quotes a index name for use in a query.
- * If the index name contains schema prefix, the prefix will also be properly quoted.
- * If the index name is already quoted or contains '(' or '{{',
- * then this method will do nothing.
- * @param string $name index name
- * @return string the properly quoted index name
- * @see quoteSimpleTableName
- */
- public function quoteIndexName($name)
- {
- if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
- return $name;
- }
- return $this->quoteSimpleIndexName($name);
- }
-
- /**
- * Quotes a column name for use in a query.
- * If the column name contains prefix, the prefix will also be properly quoted.
- * If the column name is already quoted or contains '(', '[[' or '{{',
- * then this method will do nothing.
- * @param string $name column name
- * @return string the properly quoted column name
- * @see quoteSimpleColumnName
- */
- public function quoteColumnName($name)
- {
- if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
- return $name;
- }
- if (($pos = strrpos($name, '.')) !== false) {
- $prefix = $this->quoteIndexName(substr($name, 0, $pos)) . '.';
- $name = substr($name, $pos + 1);
- } else {
- $prefix = '';
- }
- return $prefix . $this->quoteSimpleColumnName($name);
- }
-
- /**
- * Quotes a index name for use in a query.
- * A simple index name has no schema prefix.
- * @param string $name index name
- * @return string the properly quoted index name
- */
- public function quoteSimpleIndexName($name)
- {
- return strpos($name, "`") !== false ? $name : "`" . $name . "`";
- }
-
- /**
- * Quotes a column name for use in a query.
- * A simple column name has no prefix.
- * @param string $name column name
- * @return string the properly quoted column name
- */
- public function quoteSimpleColumnName($name)
- {
- return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
- }
-
- /**
- * Returns the actual name of a given index name.
- * This method will strip off curly brackets from the given index name
- * and replace the percentage character '%' with [[Connection::indexPrefix]].
- * @param string $name the index name to be converted
- * @return string the real name of the given index name
- */
- public function getRawIndexName($name)
- {
- if (strpos($name, '{{') !== false) {
- $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
- return str_replace('%', $this->db->tablePrefix, $name);
- } else {
- return $name;
- }
- }
-
- /**
- * Extracts the PHP type from abstract DB type.
- * @param ColumnSchema $column the column schema information
- * @return string PHP type name
- */
- protected function getColumnPhpType($column)
- {
- static $typeMap = [ // abstract type => php type
- 'smallint' => 'integer',
- 'integer' => 'integer',
- 'bigint' => 'integer',
- 'boolean' => 'boolean',
- 'float' => 'double',
- ];
- if (isset($typeMap[$column->type])) {
- if ($column->type === 'bigint') {
- return PHP_INT_SIZE == 8 ? 'integer' : 'string';
- } elseif ($column->type === 'integer') {
- return PHP_INT_SIZE == 4 ? 'string' : 'integer';
- } else {
- return $typeMap[$column->type];
- }
- } else {
- return 'string';
- }
- }
-
- /**
- * Collects the metadata of index columns.
- * @param IndexSchema $index the index metadata
- * @return boolean whether the index exists in the database
- * @throws \Exception if DB query fails
- */
- protected function findColumns($index)
- {
- $sql = 'DESCRIBE ' . $this->quoteSimpleIndexName($index->name);
- try {
- $columns = $this->db->createCommand($sql)->queryAll();
- } catch (\Exception $e) {
- $previous = $e->getPrevious();
- if ($previous instanceof \PDOException && $previous->getCode() == '42S02') {
- // index does not exist
- return false;
- }
- throw $e;
- }
- foreach ($columns as $info) {
- $column = $this->loadColumnSchema($info);
- $index->columns[$column->name] = $column;
- if ($column->isPrimaryKey) {
- $index->primaryKey = $column->name;
- }
- }
- return true;
- }
-
- /**
- * Loads the column information into a [[ColumnSchema]] object.
- * @param array $info column information
- * @return ColumnSchema the column schema object
- */
- protected function loadColumnSchema($info)
- {
- $column = new ColumnSchema;
-
- $column->name = $info['Field'];
- $column->dbType = $info['Type'];
-
- $column->isPrimaryKey = ($column->name == 'id');
-
- $type = $info['Type'];
- if (isset($this->typeMap[$type])) {
- $column->type = $this->typeMap[$type];
- } else {
- $column->type = self::TYPE_STRING;
- }
-
- $column->isField = ($type == 'field');
- $column->isAttribute = !$column->isField;
-
- $column->isMva = ($type == 'mva');
-
- $column->phpType = $this->getColumnPhpType($column);
-
- return $column;
- }
+ /**
+ * The followings are the supported abstract column data types.
+ */
+ const TYPE_PK = 'pk';
+ const TYPE_STRING = 'string';
+ const TYPE_INTEGER = 'integer';
+ const TYPE_BIGINT = 'bigint';
+ const TYPE_FLOAT = 'float';
+ const TYPE_TIMESTAMP = 'timestamp';
+ const TYPE_BOOLEAN = 'boolean';
+
+ /**
+ * @var Connection the Sphinx connection
+ */
+ public $db;
+ /**
+ * @var array list of ALL index names in the Sphinx
+ */
+ private $_indexNames;
+ /**
+ * @var array list of ALL index types in the Sphinx (index name => index type)
+ */
+ private $_indexTypes;
+ /**
+ * @var array list of loaded index metadata (index name => IndexSchema)
+ */
+ private $_indexes = [];
+ /**
+ * @var QueryBuilder the query builder for this Sphinx connection
+ */
+ private $_builder;
+
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ */
+ public $typeMap = [
+ 'field' => self::TYPE_STRING,
+ 'string' => self::TYPE_STRING,
+ 'ordinal' => self::TYPE_STRING,
+ 'integer' => self::TYPE_INTEGER,
+ 'int' => self::TYPE_INTEGER,
+ 'uint' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'bool' => self::TYPE_BOOLEAN,
+ 'float' => self::TYPE_FLOAT,
+ 'mva' => self::TYPE_INTEGER,
+ ];
+
+ /**
+ * Loads the metadata for the specified index.
+ * @param string $name index name
+ * @return IndexSchema driver dependent index metadata. Null if the index does not exist.
+ */
+ protected function loadIndexSchema($name)
+ {
+ $index = new IndexSchema;
+ $this->resolveIndexNames($index, $name);
+ $this->resolveIndexType($index);
+
+ if ($this->findColumns($index)) {
+ return $index;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Resolves the index name.
+ * @param IndexSchema $index the index metadata object
+ * @param string $name the index name
+ */
+ protected function resolveIndexNames($index, $name)
+ {
+ $index->name = str_replace('`', '', $name);
+ }
+
+ /**
+ * Resolves the index name.
+ * @param IndexSchema $index the index metadata object
+ */
+ protected function resolveIndexType($index)
+ {
+ $indexTypes = $this->getIndexTypes();
+ $index->type = array_key_exists($index->name, $indexTypes) ? $indexTypes[$index->name] : 'unknown';
+ $index->isRuntime = ($index->type == 'rt');
+ }
+
+ /**
+ * Obtains the metadata for the named index.
+ * @param string $name index name. The index name may contain schema name if any. Do not quote the index name.
+ * @param boolean $refresh whether to reload the index schema even if it is found in the cache.
+ * @return IndexSchema index metadata. Null if the named index does not exist.
+ */
+ public function getIndexSchema($name, $refresh = false)
+ {
+ if (isset($this->_indexes[$name]) && !$refresh) {
+ return $this->_indexes[$name];
+ }
+
+ $db = $this->db;
+ $realName = $this->getRawIndexName($name);
+
+ if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
+ /** @var $cache Cache */
+ $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
+ if ($cache instanceof Cache) {
+ $key = $this->getCacheKey($name);
+ if ($refresh || ($index = $cache->get($key)) === false) {
+ $index = $this->loadIndexSchema($realName);
+ if ($index !== null) {
+ $cache->set($key, $index, $db->schemaCacheDuration, new GroupDependency([
+ 'group' => $this->getCacheGroup(),
+ ]));
+ }
+ }
+
+ return $this->_indexes[$name] = $index;
+ }
+ }
+
+ return $this->_indexes[$name] = $this->loadIndexSchema($realName);
+ }
+
+ /**
+ * Returns the cache key for the specified index name.
+ * @param string $name the index name
+ * @return mixed the cache key
+ */
+ protected function getCacheKey($name)
+ {
+ return [
+ __CLASS__,
+ $this->db->dsn,
+ $this->db->username,
+ $name,
+ ];
+ }
+
+ /**
+ * Returns the cache group name.
+ * This allows [[refresh()]] to invalidate all cached index schemas.
+ * @return string the cache group name
+ */
+ protected function getCacheGroup()
+ {
+ return md5(serialize([
+ __CLASS__,
+ $this->db->dsn,
+ $this->db->username,
+ ]));
+ }
+
+ /**
+ * Returns the metadata for all indexes in the database.
+ * @param boolean $refresh whether to fetch the latest available index schemas. If this is false,
+ * cached data may be returned if available.
+ * @return IndexSchema[] the metadata for all indexes in the Sphinx.
+ * Each array element is an instance of [[IndexSchema]] or its child class.
+ */
+ public function getIndexSchemas($refresh = false)
+ {
+ $indexes = [];
+ foreach ($this->getIndexNames($refresh) as $name) {
+ if (($index = $this->getIndexSchema($name, $refresh)) !== null) {
+ $indexes[] = $index;
+ }
+ }
+
+ return $indexes;
+ }
+
+ /**
+ * Returns all index names in the Sphinx.
+ * @param boolean $refresh whether to fetch the latest available index names. If this is false,
+ * index names fetched previously (if available) will be returned.
+ * @return string[] all index names in the Sphinx.
+ */
+ public function getIndexNames($refresh = false)
+ {
+ if (!isset($this->_indexNames) || $refresh) {
+ $this->initIndexesInfo();
+ }
+
+ return $this->_indexNames;
+ }
+
+ /**
+ * Returns all index types in the Sphinx.
+ * @param boolean $refresh whether to fetch the latest available index types. If this is false,
+ * index types fetched previously (if available) will be returned.
+ * @return array all index types in the Sphinx in format: index name => index type.
+ */
+ public function getIndexTypes($refresh = false)
+ {
+ if (!isset($this->_indexTypes) || $refresh) {
+ $this->initIndexesInfo();
+ }
+
+ return $this->_indexTypes;
+ }
+
+ /**
+ * Initializes information about name and type of all index in the Sphinx.
+ */
+ protected function initIndexesInfo()
+ {
+ $this->_indexNames = [];
+ $this->_indexTypes = [];
+ $indexes = $this->findIndexes();
+ foreach ($indexes as $index) {
+ $indexName = $index['Index'];
+ $this->_indexNames[] = $indexName;
+ $this->_indexTypes[$indexName] = $index['Type'];
+ }
+ }
+
+ /**
+ * Returns all index names in the Sphinx.
+ * @return array all index names in the Sphinx.
+ */
+ protected function findIndexes()
+ {
+ $sql = 'SHOW TABLES';
+
+ return $this->db->createCommand($sql)->queryAll();
+ }
+
+ /**
+ * @return QueryBuilder the query builder for this connection.
+ */
+ public function getQueryBuilder()
+ {
+ if ($this->_builder === null) {
+ $this->_builder = $this->createQueryBuilder();
+ }
+
+ return $this->_builder;
+ }
+
+ /**
+ * Determines the PDO type for the given PHP data value.
+ * @param mixed $data the data whose PDO type is to be determined
+ * @return integer the PDO type
+ * @see http://www.php.net/manual/en/pdo.constants.php
+ */
+ public function getPdoType($data)
+ {
+ static $typeMap = [
+ // php type => PDO type
+ 'boolean' => \PDO::PARAM_BOOL,
+ 'integer' => \PDO::PARAM_INT,
+ 'string' => \PDO::PARAM_STR,
+ 'resource' => \PDO::PARAM_LOB,
+ 'NULL' => \PDO::PARAM_NULL,
+ ];
+ $type = gettype($data);
+
+ return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
+ }
+
+ /**
+ * Refreshes the schema.
+ * This method cleans up all cached index schemas so that they can be re-created later
+ * to reflect the Sphinx schema change.
+ */
+ public function refresh()
+ {
+ /** @var $cache Cache */
+ $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
+ if ($this->db->enableSchemaCache && $cache instanceof Cache) {
+ GroupDependency::invalidate($cache, $this->getCacheGroup());
+ }
+ $this->_indexNames = [];
+ $this->_indexes = [];
+ }
+
+ /**
+ * Creates a query builder for the Sphinx.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.PDO-quote.php
+ */
+ public function quoteValue($str)
+ {
+ if (!is_string($str)) {
+ return $str;
+ }
+ $this->db->open();
+
+ return $this->db->pdo->quote($str);
+ }
+
+ /**
+ * Quotes a index name for use in a query.
+ * If the index name contains schema prefix, the prefix will also be properly quoted.
+ * If the index name is already quoted or contains '(' or '{{',
+ * then this method will do nothing.
+ * @param string $name index name
+ * @return string the properly quoted index name
+ * @see quoteSimpleTableName
+ */
+ public function quoteIndexName($name)
+ {
+ if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
+ return $name;
+ }
+
+ return $this->quoteSimpleIndexName($name);
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * If the column name contains prefix, the prefix will also be properly quoted.
+ * If the column name is already quoted or contains '(', '[[' or '{{',
+ * then this method will do nothing.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ * @see quoteSimpleColumnName
+ */
+ public function quoteColumnName($name)
+ {
+ if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
+ return $name;
+ }
+ if (($pos = strrpos($name, '.')) !== false) {
+ $prefix = $this->quoteIndexName(substr($name, 0, $pos)) . '.';
+ $name = substr($name, $pos + 1);
+ } else {
+ $prefix = '';
+ }
+
+ return $prefix . $this->quoteSimpleColumnName($name);
+ }
+
+ /**
+ * Quotes a index name for use in a query.
+ * A simple index name has no schema prefix.
+ * @param string $name index name
+ * @return string the properly quoted index name
+ */
+ public function quoteSimpleIndexName($name)
+ {
+ return strpos($name, "`") !== false ? $name : "`" . $name . "`";
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
+ }
+
+ /**
+ * Returns the actual name of a given index name.
+ * This method will strip off curly brackets from the given index name
+ * and replace the percentage character '%' with [[Connection::indexPrefix]].
+ * @param string $name the index name to be converted
+ * @return string the real name of the given index name
+ */
+ public function getRawIndexName($name)
+ {
+ if (strpos($name, '{{') !== false) {
+ $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
+
+ return str_replace('%', $this->db->tablePrefix, $name);
+ } else {
+ return $name;
+ }
+ }
+
+ /**
+ * Extracts the PHP type from abstract DB type.
+ * @param ColumnSchema $column the column schema information
+ * @return string PHP type name
+ */
+ protected function getColumnPhpType($column)
+ {
+ static $typeMap = [ // abstract type => php type
+ 'smallint' => 'integer',
+ 'integer' => 'integer',
+ 'bigint' => 'integer',
+ 'boolean' => 'boolean',
+ 'float' => 'double',
+ ];
+ if (isset($typeMap[$column->type])) {
+ if ($column->type === 'bigint') {
+ return PHP_INT_SIZE == 8 ? 'integer' : 'string';
+ } elseif ($column->type === 'integer') {
+ return PHP_INT_SIZE == 4 ? 'string' : 'integer';
+ } else {
+ return $typeMap[$column->type];
+ }
+ } else {
+ return 'string';
+ }
+ }
+
+ /**
+ * Collects the metadata of index columns.
+ * @param IndexSchema $index the index metadata
+ * @return boolean whether the index exists in the database
+ * @throws \Exception if DB query fails
+ */
+ protected function findColumns($index)
+ {
+ $sql = 'DESCRIBE ' . $this->quoteSimpleIndexName($index->name);
+ try {
+ $columns = $this->db->createCommand($sql)->queryAll();
+ } catch (\Exception $e) {
+ $previous = $e->getPrevious();
+ if ($previous instanceof \PDOException && $previous->getCode() == '42S02') {
+ // index does not exist
+ return false;
+ }
+ throw $e;
+ }
+ foreach ($columns as $info) {
+ $column = $this->loadColumnSchema($info);
+ $index->columns[$column->name] = $column;
+ if ($column->isPrimaryKey) {
+ $index->primaryKey = $column->name;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema;
+
+ $column->name = $info['Field'];
+ $column->dbType = $info['Type'];
+
+ $column->isPrimaryKey = ($column->name == 'id');
+
+ $type = $info['Type'];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ } else {
+ $column->type = self::TYPE_STRING;
+ }
+
+ $column->isField = ($type == 'field');
+ $column->isAttribute = !$column->isField;
+
+ $column->isMva = ($type == 'mva');
+
+ $column->phpType = $this->getColumnPhpType($column);
+
+ return $column;
+ }
}
diff --git a/extensions/swiftmailer/Mailer.php b/extensions/swiftmailer/Mailer.php
index 8a87ba251ef..1f91f6d86cd 100644
--- a/extensions/swiftmailer/Mailer.php
+++ b/extensions/swiftmailer/Mailer.php
@@ -75,146 +75,151 @@
*/
class Mailer extends BaseMailer
{
- /**
- * @var string message default class name.
- */
- public $messageClass = 'yii\swiftmailer\Message';
- /**
- * @var \Swift_Mailer Swift mailer instance.
- */
- private $_swiftMailer;
- /**
- * @var \Swift_Transport|array Swift transport instance or its array configuration.
- */
- private $_transport = [];
+ /**
+ * @var string message default class name.
+ */
+ public $messageClass = 'yii\swiftmailer\Message';
+ /**
+ * @var \Swift_Mailer Swift mailer instance.
+ */
+ private $_swiftMailer;
+ /**
+ * @var \Swift_Transport|array Swift transport instance or its array configuration.
+ */
+ private $_transport = [];
- /**
- * @return array|\Swift_Mailer Swift mailer instance or array configuration.
- */
- public function getSwiftMailer()
- {
- if (!is_object($this->_swiftMailer)) {
- $this->_swiftMailer = $this->createSwiftMailer();
- }
- return $this->_swiftMailer;
- }
+ /**
+ * @return array|\Swift_Mailer Swift mailer instance or array configuration.
+ */
+ public function getSwiftMailer()
+ {
+ if (!is_object($this->_swiftMailer)) {
+ $this->_swiftMailer = $this->createSwiftMailer();
+ }
- /**
- * @param array|\Swift_Transport $transport
- * @throws InvalidConfigException on invalid argument.
- */
- public function setTransport($transport)
- {
- if (!is_array($transport) && !is_object($transport)) {
- throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.');
- }
- $this->_transport = $transport;
- }
+ return $this->_swiftMailer;
+ }
- /**
- * @return array|\Swift_Transport
- */
- public function getTransport()
- {
- if (!is_object($this->_transport)) {
- $this->_transport = $this->createTransport($this->_transport);
- }
- return $this->_transport;
- }
+ /**
+ * @param array|\Swift_Transport $transport
+ * @throws InvalidConfigException on invalid argument.
+ */
+ public function setTransport($transport)
+ {
+ if (!is_array($transport) && !is_object($transport)) {
+ throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.');
+ }
+ $this->_transport = $transport;
+ }
- /**
- * @inheritdoc
- */
- protected function sendMessage($message)
- {
- $address = $message->getTo();
- if (is_array($address)) {
- $address = implode(', ', array_keys($address));
- }
- Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__);
- return $this->getSwiftMailer()->send($message->getSwiftMessage()) > 0;
- }
+ /**
+ * @return array|\Swift_Transport
+ */
+ public function getTransport()
+ {
+ if (!is_object($this->_transport)) {
+ $this->_transport = $this->createTransport($this->_transport);
+ }
- /**
- * Creates Swift mailer instance.
- * @return \Swift_Mailer mailer instance.
- */
- protected function createSwiftMailer()
- {
- return \Swift_Mailer::newInstance($this->getTransport());
- }
+ return $this->_transport;
+ }
- /**
- * Creates email transport instance by its array configuration.
- * @param array $config transport configuration.
- * @throws \yii\base\InvalidConfigException on invalid transport configuration.
- * @return \Swift_Transport transport instance.
- */
- protected function createTransport(array $config)
- {
- if (!isset($config['class'])) {
- $config['class'] = 'Swift_MailTransport';
- }
- if (isset($config['plugins'])) {
- $plugins = $config['plugins'];
- unset($config['plugins']);
- }
- /** @var \Swift_MailTransport $transport */
- $transport = $this->createSwiftObject($config);
- if (isset($plugins)) {
- foreach ($plugins as $plugin) {
- if (is_array($plugin) && isset($plugin['class'])) {
- $plugin = $this->createSwiftObject($plugin);
- }
- $transport->registerPlugin($plugin);
- }
- }
- return $transport;
- }
+ /**
+ * @inheritdoc
+ */
+ protected function sendMessage($message)
+ {
+ $address = $message->getTo();
+ if (is_array($address)) {
+ $address = implode(', ', array_keys($address));
+ }
+ Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__);
- /**
- * Creates Swift library object, from given array configuration.
- * @param array $config object configuration
- * @return Object created object
- * @throws \yii\base\InvalidConfigException on invalid configuration.
- */
- protected function createSwiftObject(array $config)
- {
- if (isset($config['class'])) {
- $className = $config['class'];
- unset($config['class']);
- } else {
- throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
- }
- if (isset($config['constructArgs'])) {
- $args = [];
- foreach ($config['constructArgs'] as $arg) {
- if (is_array($arg) && isset($arg['class'])) {
- $args[] = $this->createSwiftObject($arg);
- } else {
- $args[] = $arg;
- }
- }
- unset($config['constructArgs']);
- array_unshift($args, $className);
- $object = call_user_func_array(['Yii', 'createObject'], $args);
- } else {
- $object = new $className;
- }
- if (!empty($config)) {
- foreach ($config as $name => $value) {
- if (property_exists($object, $name)) {
- $object->$name = $value;
- } else {
- $setter = 'set' . $name;
- if (method_exists($object, $setter) || method_exists($object, '__call')) {
- $object->$setter($value);
- } else {
- throw new InvalidConfigException('Setting unknown property: ' . $className . '::' . $name);
- }
- }
- }
- }
- return $object;
- }
+ return $this->getSwiftMailer()->send($message->getSwiftMessage()) > 0;
+ }
+
+ /**
+ * Creates Swift mailer instance.
+ * @return \Swift_Mailer mailer instance.
+ */
+ protected function createSwiftMailer()
+ {
+ return \Swift_Mailer::newInstance($this->getTransport());
+ }
+
+ /**
+ * Creates email transport instance by its array configuration.
+ * @param array $config transport configuration.
+ * @throws \yii\base\InvalidConfigException on invalid transport configuration.
+ * @return \Swift_Transport transport instance.
+ */
+ protected function createTransport(array $config)
+ {
+ if (!isset($config['class'])) {
+ $config['class'] = 'Swift_MailTransport';
+ }
+ if (isset($config['plugins'])) {
+ $plugins = $config['plugins'];
+ unset($config['plugins']);
+ }
+ /** @var \Swift_MailTransport $transport */
+ $transport = $this->createSwiftObject($config);
+ if (isset($plugins)) {
+ foreach ($plugins as $plugin) {
+ if (is_array($plugin) && isset($plugin['class'])) {
+ $plugin = $this->createSwiftObject($plugin);
+ }
+ $transport->registerPlugin($plugin);
+ }
+ }
+
+ return $transport;
+ }
+
+ /**
+ * Creates Swift library object, from given array configuration.
+ * @param array $config object configuration
+ * @return Object created object
+ * @throws \yii\base\InvalidConfigException on invalid configuration.
+ */
+ protected function createSwiftObject(array $config)
+ {
+ if (isset($config['class'])) {
+ $className = $config['class'];
+ unset($config['class']);
+ } else {
+ throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
+ }
+ if (isset($config['constructArgs'])) {
+ $args = [];
+ foreach ($config['constructArgs'] as $arg) {
+ if (is_array($arg) && isset($arg['class'])) {
+ $args[] = $this->createSwiftObject($arg);
+ } else {
+ $args[] = $arg;
+ }
+ }
+ unset($config['constructArgs']);
+ array_unshift($args, $className);
+ $object = call_user_func_array(['Yii', 'createObject'], $args);
+ } else {
+ $object = new $className;
+ }
+ if (!empty($config)) {
+ foreach ($config as $name => $value) {
+ if (property_exists($object, $name)) {
+ $object->$name = $value;
+ } else {
+ $setter = 'set' . $name;
+ if (method_exists($object, $setter) || method_exists($object, '__call')) {
+ $object->$setter($value);
+ } else {
+ throw new InvalidConfigException('Setting unknown property: ' . $className . '::' . $name);
+ }
+ }
+ }
+ }
+
+ return $object;
+ }
}
diff --git a/extensions/swiftmailer/Message.php b/extensions/swiftmailer/Message.php
index 7269671bd5c..361a0212f05 100644
--- a/extensions/swiftmailer/Message.php
+++ b/extensions/swiftmailer/Message.php
@@ -24,281 +24,295 @@
*/
class Message extends BaseMessage
{
- /**
- * @var \Swift_Message Swift message instance.
- */
- private $_swiftMessage;
-
- /**
- * @return \Swift_Message Swift message instance.
- */
- public function getSwiftMessage()
- {
- if (!is_object($this->_swiftMessage)) {
- $this->_swiftMessage = $this->createSwiftMessage();
- }
- return $this->_swiftMessage;
- }
-
- /**
- * @inheritdoc
- */
- public function getCharset()
- {
- return $this->getSwiftMessage()->getCharset();
- }
-
- /**
- * @inheritdoc
- */
- public function setCharset($charset)
- {
- $this->getSwiftMessage()->setCharset($charset);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function getFrom()
- {
- return $this->getSwiftMessage()->getFrom();
- }
-
- /**
- * @inheritdoc
- */
- public function setFrom($from)
- {
- $this->getSwiftMessage()->setFrom($from);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function getReplyTo()
- {
- return $this->getSwiftMessage()->getReplyTo();
- }
-
- /**
- * @inheritdoc
- */
- public function setReplyTo($replyTo)
- {
- $this->getSwiftMessage()->setReplyTo($replyTo);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function getTo()
- {
- return $this->getSwiftMessage()->getTo();
- }
-
- /**
- * @inheritdoc
- */
- public function setTo($to)
- {
- $this->getSwiftMessage()->setTo($to);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function getCc()
- {
- return $this->getSwiftMessage()->getCc();
- }
-
- /**
- * @inheritdoc
- */
- public function setCc($cc)
- {
- $this->getSwiftMessage()->setCc($cc);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function getBcc()
- {
- return $this->getSwiftMessage()->getBcc();
- }
-
- /**
- * @inheritdoc
- */
- public function setBcc($bcc)
- {
- $this->getSwiftMessage()->setBcc($bcc);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function getSubject()
- {
- return $this->getSwiftMessage()->getSubject();
- }
-
- /**
- * @inheritdoc
- */
- public function setSubject($subject)
- {
- $this->getSwiftMessage()->setSubject($subject);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function setTextBody($text)
- {
- $this->setBody($text, 'text/plain');
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function setHtmlBody($html)
- {
- $this->setBody($html, 'text/html');
- return $this;
- }
-
- /**
- * Sets the message body.
- * If body is already set and its content type matches given one, it will
- * be overridden, if content type miss match the multipart message will be composed.
- * @param string $body body content.
- * @param string $contentType body content type.
- */
- protected function setBody($body, $contentType)
- {
- $message = $this->getSwiftMessage();
- $oldBody = $message->getBody();
- $charset = $message->getCharset();
- if (empty($oldBody)) {
- $parts = $message->getChildren();
- $partFound = false;
- foreach ($parts as $key => $part) {
- if (!($part instanceof \Swift_Mime_Attachment)) {
- /* @var \Swift_Mime_MimePart $part */
- if ($part->getContentType() == $contentType) {
- $charset = $part->getCharset();
- unset($parts[$key]);
- $partFound = true;
- break;
- }
- }
- }
- if ($partFound) {
- reset($parts);
- $message->setChildren($parts);
- $message->addPart($body, $contentType, $charset);
- } else {
- $message->setBody($body, $contentType);
- }
- } else {
- $oldContentType = $message->getContentType();
- if ($oldContentType == $contentType) {
- $message->setBody($body, $contentType);
- } else {
- $message->setBody(null);
- $message->setContentType(null);
- $message->addPart($oldBody, $oldContentType, $charset);
- $message->addPart($body, $contentType, $charset);
- }
- }
- }
-
- /**
- * @inheritdoc
- */
- public function attach($fileName, array $options = [])
- {
- $attachment = \Swift_Attachment::fromPath($fileName);
- if (!empty($options['fileName'])) {
- $attachment->setFilename($options['fileName']);
- }
- if (!empty($options['contentType'])) {
- $attachment->setContentType($options['contentType']);
- }
- $this->getSwiftMessage()->attach($attachment);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function attachContent($content, array $options = [])
- {
- $attachment = \Swift_Attachment::newInstance($content);
- if (!empty($options['fileName'])) {
- $attachment->setFilename($options['fileName']);
- }
- if (!empty($options['contentType'])) {
- $attachment->setContentType($options['contentType']);
- }
- $this->getSwiftMessage()->attach($attachment);
- return $this;
- }
-
- /**
- * @inheritdoc
- */
- public function embed($fileName, array $options = [])
- {
- $embedFile = \Swift_EmbeddedFile::fromPath($fileName);
- if (!empty($options['fileName'])) {
- $embedFile->setFilename($options['fileName']);
- }
- if (!empty($options['contentType'])) {
- $embedFile->setContentType($options['contentType']);
- }
- return $this->getSwiftMessage()->embed($embedFile);
- }
-
- /**
- * @inheritdoc
- */
- public function embedContent($content, array $options = [])
- {
- $embedFile = \Swift_EmbeddedFile::newInstance($content);
- if (!empty($options['fileName'])) {
- $embedFile->setFilename($options['fileName']);
- }
- if (!empty($options['contentType'])) {
- $embedFile->setContentType($options['contentType']);
- }
- return $this->getSwiftMessage()->embed($embedFile);
- }
-
- /**
- * @inheritdoc
- */
- public function toString()
- {
- return $this->getSwiftMessage()->toString();
- }
-
- /**
- * Creates the Swift email message instance.
- * @return \Swift_Message email message instance.
- */
- protected function createSwiftMessage()
- {
- return new \Swift_Message();
- }
+ /**
+ * @var \Swift_Message Swift message instance.
+ */
+ private $_swiftMessage;
+
+ /**
+ * @return \Swift_Message Swift message instance.
+ */
+ public function getSwiftMessage()
+ {
+ if (!is_object($this->_swiftMessage)) {
+ $this->_swiftMessage = $this->createSwiftMessage();
+ }
+
+ return $this->_swiftMessage;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCharset()
+ {
+ return $this->getSwiftMessage()->getCharset();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setCharset($charset)
+ {
+ $this->getSwiftMessage()->setCharset($charset);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getFrom()
+ {
+ return $this->getSwiftMessage()->getFrom();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setFrom($from)
+ {
+ $this->getSwiftMessage()->setFrom($from);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getReplyTo()
+ {
+ return $this->getSwiftMessage()->getReplyTo();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setReplyTo($replyTo)
+ {
+ $this->getSwiftMessage()->setReplyTo($replyTo);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTo()
+ {
+ return $this->getSwiftMessage()->getTo();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setTo($to)
+ {
+ $this->getSwiftMessage()->setTo($to);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCc()
+ {
+ return $this->getSwiftMessage()->getCc();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setCc($cc)
+ {
+ $this->getSwiftMessage()->setCc($cc);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getBcc()
+ {
+ return $this->getSwiftMessage()->getBcc();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setBcc($bcc)
+ {
+ $this->getSwiftMessage()->setBcc($bcc);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSubject()
+ {
+ return $this->getSwiftMessage()->getSubject();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setSubject($subject)
+ {
+ $this->getSwiftMessage()->setSubject($subject);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setTextBody($text)
+ {
+ $this->setBody($text, 'text/plain');
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setHtmlBody($html)
+ {
+ $this->setBody($html, 'text/html');
+
+ return $this;
+ }
+
+ /**
+ * Sets the message body.
+ * If body is already set and its content type matches given one, it will
+ * be overridden, if content type miss match the multipart message will be composed.
+ * @param string $body body content.
+ * @param string $contentType body content type.
+ */
+ protected function setBody($body, $contentType)
+ {
+ $message = $this->getSwiftMessage();
+ $oldBody = $message->getBody();
+ $charset = $message->getCharset();
+ if (empty($oldBody)) {
+ $parts = $message->getChildren();
+ $partFound = false;
+ foreach ($parts as $key => $part) {
+ if (!($part instanceof \Swift_Mime_Attachment)) {
+ /* @var \Swift_Mime_MimePart $part */
+ if ($part->getContentType() == $contentType) {
+ $charset = $part->getCharset();
+ unset($parts[$key]);
+ $partFound = true;
+ break;
+ }
+ }
+ }
+ if ($partFound) {
+ reset($parts);
+ $message->setChildren($parts);
+ $message->addPart($body, $contentType, $charset);
+ } else {
+ $message->setBody($body, $contentType);
+ }
+ } else {
+ $oldContentType = $message->getContentType();
+ if ($oldContentType == $contentType) {
+ $message->setBody($body, $contentType);
+ } else {
+ $message->setBody(null);
+ $message->setContentType(null);
+ $message->addPart($oldBody, $oldContentType, $charset);
+ $message->addPart($body, $contentType, $charset);
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attach($fileName, array $options = [])
+ {
+ $attachment = \Swift_Attachment::fromPath($fileName);
+ if (!empty($options['fileName'])) {
+ $attachment->setFilename($options['fileName']);
+ }
+ if (!empty($options['contentType'])) {
+ $attachment->setContentType($options['contentType']);
+ }
+ $this->getSwiftMessage()->attach($attachment);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attachContent($content, array $options = [])
+ {
+ $attachment = \Swift_Attachment::newInstance($content);
+ if (!empty($options['fileName'])) {
+ $attachment->setFilename($options['fileName']);
+ }
+ if (!empty($options['contentType'])) {
+ $attachment->setContentType($options['contentType']);
+ }
+ $this->getSwiftMessage()->attach($attachment);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function embed($fileName, array $options = [])
+ {
+ $embedFile = \Swift_EmbeddedFile::fromPath($fileName);
+ if (!empty($options['fileName'])) {
+ $embedFile->setFilename($options['fileName']);
+ }
+ if (!empty($options['contentType'])) {
+ $embedFile->setContentType($options['contentType']);
+ }
+
+ return $this->getSwiftMessage()->embed($embedFile);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function embedContent($content, array $options = [])
+ {
+ $embedFile = \Swift_EmbeddedFile::newInstance($content);
+ if (!empty($options['fileName'])) {
+ $embedFile->setFilename($options['fileName']);
+ }
+ if (!empty($options['contentType'])) {
+ $embedFile->setContentType($options['contentType']);
+ }
+
+ return $this->getSwiftMessage()->embed($embedFile);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function toString()
+ {
+ return $this->getSwiftMessage()->toString();
+ }
+
+ /**
+ * Creates the Swift email message instance.
+ * @return \Swift_Message email message instance.
+ */
+ protected function createSwiftMessage()
+ {
+ return new \Swift_Message();
+ }
}
diff --git a/extensions/twig/TwigSimpleFileLoader.php b/extensions/twig/TwigSimpleFileLoader.php
index 0780ff7ab60..819c9495bdb 100644
--- a/extensions/twig/TwigSimpleFileLoader.php
+++ b/extensions/twig/TwigSimpleFileLoader.php
@@ -9,7 +9,6 @@
namespace yii\twig;
-
/**
* Twig view file loader class.
*
@@ -17,59 +16,59 @@
*/
class TwigSimpleFileLoader implements \Twig_LoaderInterface
{
- /**
- * @var string Path to directory
- */
- private $_dir;
+ /**
+ * @var string Path to directory
+ */
+ private $_dir;
- /**
- * @param string $dir path to directory
- */
- public function __construct($dir)
- {
- $this->_dir = $dir;
- }
+ /**
+ * @param string $dir path to directory
+ */
+ public function __construct($dir)
+ {
+ $this->_dir = $dir;
+ }
- /**
- * Compare a file's freshness with previously stored timestamp
- *
- * @param string $name file name to check
- * @param integer $time timestamp to compare with
- * @return boolean true if file is still fresh and not changes, false otherwise
- */
- public function isFresh($name, $time)
- {
- return filemtime($this->getFilePath($name)) <= $time;
- }
+ /**
+ * Compare a file's freshness with previously stored timestamp
+ *
+ * @param string $name file name to check
+ * @param integer $time timestamp to compare with
+ * @return boolean true if file is still fresh and not changes, false otherwise
+ */
+ public function isFresh($name, $time)
+ {
+ return filemtime($this->getFilePath($name)) <= $time;
+ }
- /**
- * Get the source of given file name
- *
- * @param string $name file name
- * @return string contents of given file name
- */
- public function getSource($name)
- {
- return file_get_contents($this->getFilePath($name));
- }
+ /**
+ * Get the source of given file name
+ *
+ * @param string $name file name
+ * @return string contents of given file name
+ */
+ public function getSource($name)
+ {
+ return file_get_contents($this->getFilePath($name));
+ }
- /**
- * Get unique key that can represent this file uniquely among other files.
- * @param string $name
- * @return string
- */
- public function getCacheKey($name)
- {
- return $this->getFilePath($name);
- }
+ /**
+ * Get unique key that can represent this file uniquely among other files.
+ * @param string $name
+ * @return string
+ */
+ public function getCacheKey($name)
+ {
+ return $this->getFilePath($name);
+ }
- /**
- * internally used to get absolute path of given file name
- * @param string $name file name
- * @return string absolute path of file
- */
- protected function getFilePath($name)
- {
- return $this->_dir . '/' . $name;
- }
+ /**
+ * internally used to get absolute path of given file name
+ * @param string $name file name
+ * @return string absolute path of file
+ */
+ protected function getFilePath($name)
+ {
+ return $this->_dir . '/' . $name;
+ }
}
diff --git a/extensions/twig/ViewRenderer.php b/extensions/twig/ViewRenderer.php
index 2a487654ac5..786d3412827 100644
--- a/extensions/twig/ViewRenderer.php
+++ b/extensions/twig/ViewRenderer.php
@@ -24,211 +24,211 @@
*/
class ViewRenderer extends BaseViewRenderer
{
- /**
- * @var string the directory or path alias pointing to where Twig cache will be stored.
- */
- public $cachePath = '@runtime/Twig/cache';
- /**
- * @var array Twig options.
- * @see http://twig.sensiolabs.org/doc/api.html#environment-options
- */
- public $options = [];
- /**
- * @var array Objects or static classes.
- * Keys of the array are names to call in template, values are objects or names of static classes.
- * Example: `['html' => '\yii\helpers\Html']`.
- * In the template you can use it like this: `{{ html.a('Login', 'site/login') | raw }}`.
- */
- public $globals = [];
- /**
- * @var array Custom functions.
- * Keys of the array are names to call in template, values are names of functions or static methods of some class.
- * Example: `['rot13' => 'str_rot13', 'a' => '\yii\helpers\Html::a']`.
- * In the template you can use it like this: `{{ rot13('test') }}` or `{{ a('Login', 'site/login') | raw }}`.
- */
- public $functions = [];
- /**
- * @var array Custom filters.
- * Keys of the array are names to call in template, values are names of functions or static methods of some class.
- * Example: `['rot13' => 'str_rot13', 'jsonEncode' => '\yii\helpers\Json::encode']`.
- * In the template you can use it like this: `{{ 'test'|rot13 }}` or `{{ model|jsonEncode }}`.
- */
- public $filters = [];
- /**
- * @var array Custom extensions.
- * Example: `['Twig_Extension_Sandbox', 'Twig_Extension_Text']`
- */
- public $extensions = [];
- /**
- * @var array Twig lexer options.
- * Example: Smarty-like syntax:
- * ```php
- * [
- * 'tag_comment' => ['{*', '*}'],
- * 'tag_block' => ['{', '}'],
- * 'tag_variable' => ['{$', '}']
- * ]
- * ```
- * @see http://twig.sensiolabs.org/doc/recipes.html#customizing-the-syntax
- */
- public $lexerOptions = [];
- /**
- * @var \Twig_Environment twig environment object that do all rendering twig templates
- */
- public $twig;
-
-
- public function init()
- {
- $this->twig = new \Twig_Environment(null, array_merge([
- 'cache' => Yii::getAlias($this->cachePath),
- 'charset' => Yii::$app->charset,
- ], $this->options));
-
- // Adding custom extensions
- if (!empty($this->extensions)) {
- foreach ($this->extensions as $extension) {
- $this->twig->addExtension(new $extension());
- }
- }
-
- // Adding custom globals (objects or static classes)
- if (!empty($this->globals)) {
- $this->addGlobals($this->globals);
- }
-
- // Adding custom functions
- if (!empty($this->functions)) {
- $this->addFunctions($this->functions);
- }
-
- // Adding custom filters
- if (!empty($this->filters)) {
- $this->addFilters($this->filters);
- }
-
- // Adding custom extensions
- if (!empty($this->extensions)) {
- $this->addExtensions($this->extensions);
- }
-
- // Change lexer syntax
- if (!empty($this->lexerOptions)) {
- $this->setLexerOptions($this->lexerOptions);
- }
-
- // Adding global 'void' function (usage: {{void(App.clientScript.registerScriptFile(...))}})
- $this->twig->addFunction('void', new \Twig_Function_Function(function ($argument) {
- }));
-
- $this->twig->addFunction('path', new \Twig_Function_Function(function ($path, $args = []) {
- return Url::to(array_merge([$path], $args));
- }));
-
- $this->twig->addGlobal('app', \Yii::$app);
- }
-
- /**
- * Renders a view file.
- *
- * This method is invoked by [[View]] whenever it tries to render a view.
- * Child classes must implement this method to render the given view file.
- *
- * @param View $view the view object used for rendering the file.
- * @param string $file the view file.
- * @param array $params the parameters to be passed to the view file.
- *
- * @return string the rendering result
- */
- public function render($view, $file, $params)
- {
- $this->twig->addGlobal('this', $view);
- $this->twig->setLoader(new TwigSimpleFileLoader(dirname($file)));
- return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params);
- }
-
- /**
- * Adds global objects or static classes
- * @param array $globals @see self::$globals
- */
- public function addGlobals($globals)
- {
- foreach ($globals as $name => $value) {
- if (!is_object($value)) {
- $value = new ViewRendererStaticClassProxy($value);
- }
- $this->twig->addGlobal($name, $value);
- }
- }
-
- /**
- * Adds custom functions
- * @param array $functions @see self::$functions
- */
- public function addFunctions($functions)
- {
- $this->_addCustom('Function', $functions);
- }
-
- /**
- * Adds custom filters
- * @param array $filters @see self::$filters
- */
- public function addFilters($filters)
- {
- $this->_addCustom('Filter', $filters);
- }
-
- /**
- * Adds custom extensions
- * @param array $extensions @see self::$extensions
- */
- public function addExtensions($extensions)
- {
- foreach ($extensions as $extName) {
- $this->twig->addExtension(new $extName());
- }
- }
-
- /**
- * Sets Twig lexer options to change templates syntax
- * @param array $options @see self::$lexerOptions
- */
- public function setLexerOptions($options)
- {
- $lexer = new \Twig_Lexer($this->twig, $options);
- $this->twig->setLexer($lexer);
- }
-
- /**
- * Adds custom function or filter
- * @param string $classType 'Function' or 'Filter'
- * @param array $elements Parameters of elements to add
- * @throws \Exception
- */
- private function _addCustom($classType, $elements)
- {
- $classFunction = 'Twig_' . $classType . '_Function';
-
- foreach ($elements as $name => $func) {
- $twigElement = null;
-
- switch ($func) {
- // Just a name of function
- case is_string($func):
- $twigElement = new $classFunction($func);
- break;
- // Name of function + options array
- case is_array($func) && is_string($func[0]) && isset($func[1]) && is_array($func[1]):
- $twigElement = new $classFunction($func[0], $func[1]);
- break;
- }
-
- if ($twigElement !== null) {
- $this->twig->{'add'.$classType}($name, $twigElement);
- } else {
- throw new \Exception("Incorrect options for \"$classType\" $name.");
- }
- }
- }
+ /**
+ * @var string the directory or path alias pointing to where Twig cache will be stored.
+ */
+ public $cachePath = '@runtime/Twig/cache';
+ /**
+ * @var array Twig options.
+ * @see http://twig.sensiolabs.org/doc/api.html#environment-options
+ */
+ public $options = [];
+ /**
+ * @var array Objects or static classes.
+ * Keys of the array are names to call in template, values are objects or names of static classes.
+ * Example: `['html' => '\yii\helpers\Html']`.
+ * In the template you can use it like this: `{{ html.a('Login', 'site/login') | raw }}`.
+ */
+ public $globals = [];
+ /**
+ * @var array Custom functions.
+ * Keys of the array are names to call in template, values are names of functions or static methods of some class.
+ * Example: `['rot13' => 'str_rot13', 'a' => '\yii\helpers\Html::a']`.
+ * In the template you can use it like this: `{{ rot13('test') }}` or `{{ a('Login', 'site/login') | raw }}`.
+ */
+ public $functions = [];
+ /**
+ * @var array Custom filters.
+ * Keys of the array are names to call in template, values are names of functions or static methods of some class.
+ * Example: `['rot13' => 'str_rot13', 'jsonEncode' => '\yii\helpers\Json::encode']`.
+ * In the template you can use it like this: `{{ 'test'|rot13 }}` or `{{ model|jsonEncode }}`.
+ */
+ public $filters = [];
+ /**
+ * @var array Custom extensions.
+ * Example: `['Twig_Extension_Sandbox', 'Twig_Extension_Text']`
+ */
+ public $extensions = [];
+ /**
+ * @var array Twig lexer options.
+ * Example: Smarty-like syntax:
+ * ```php
+ * [
+ * 'tag_comment' => ['{*', '*}'],
+ * 'tag_block' => ['{', '}'],
+ * 'tag_variable' => ['{$', '}']
+ * ]
+ * ```
+ * @see http://twig.sensiolabs.org/doc/recipes.html#customizing-the-syntax
+ */
+ public $lexerOptions = [];
+ /**
+ * @var \Twig_Environment twig environment object that do all rendering twig templates
+ */
+ public $twig;
+
+ public function init()
+ {
+ $this->twig = new \Twig_Environment(null, array_merge([
+ 'cache' => Yii::getAlias($this->cachePath),
+ 'charset' => Yii::$app->charset,
+ ], $this->options));
+
+ // Adding custom extensions
+ if (!empty($this->extensions)) {
+ foreach ($this->extensions as $extension) {
+ $this->twig->addExtension(new $extension());
+ }
+ }
+
+ // Adding custom globals (objects or static classes)
+ if (!empty($this->globals)) {
+ $this->addGlobals($this->globals);
+ }
+
+ // Adding custom functions
+ if (!empty($this->functions)) {
+ $this->addFunctions($this->functions);
+ }
+
+ // Adding custom filters
+ if (!empty($this->filters)) {
+ $this->addFilters($this->filters);
+ }
+
+ // Adding custom extensions
+ if (!empty($this->extensions)) {
+ $this->addExtensions($this->extensions);
+ }
+
+ // Change lexer syntax
+ if (!empty($this->lexerOptions)) {
+ $this->setLexerOptions($this->lexerOptions);
+ }
+
+ // Adding global 'void' function (usage: {{void(App.clientScript.registerScriptFile(...))}})
+ $this->twig->addFunction('void', new \Twig_Function_Function(function ($argument) {
+ }));
+
+ $this->twig->addFunction('path', new \Twig_Function_Function(function ($path, $args = []) {
+ return Url::to(array_merge([$path], $args));
+ }));
+
+ $this->twig->addGlobal('app', \Yii::$app);
+ }
+
+ /**
+ * Renders a view file.
+ *
+ * This method is invoked by [[View]] whenever it tries to render a view.
+ * Child classes must implement this method to render the given view file.
+ *
+ * @param View $view the view object used for rendering the file.
+ * @param string $file the view file.
+ * @param array $params the parameters to be passed to the view file.
+ *
+ * @return string the rendering result
+ */
+ public function render($view, $file, $params)
+ {
+ $this->twig->addGlobal('this', $view);
+ $this->twig->setLoader(new TwigSimpleFileLoader(dirname($file)));
+
+ return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params);
+ }
+
+ /**
+ * Adds global objects or static classes
+ * @param array $globals @see self::$globals
+ */
+ public function addGlobals($globals)
+ {
+ foreach ($globals as $name => $value) {
+ if (!is_object($value)) {
+ $value = new ViewRendererStaticClassProxy($value);
+ }
+ $this->twig->addGlobal($name, $value);
+ }
+ }
+
+ /**
+ * Adds custom functions
+ * @param array $functions @see self::$functions
+ */
+ public function addFunctions($functions)
+ {
+ $this->_addCustom('Function', $functions);
+ }
+
+ /**
+ * Adds custom filters
+ * @param array $filters @see self::$filters
+ */
+ public function addFilters($filters)
+ {
+ $this->_addCustom('Filter', $filters);
+ }
+
+ /**
+ * Adds custom extensions
+ * @param array $extensions @see self::$extensions
+ */
+ public function addExtensions($extensions)
+ {
+ foreach ($extensions as $extName) {
+ $this->twig->addExtension(new $extName());
+ }
+ }
+
+ /**
+ * Sets Twig lexer options to change templates syntax
+ * @param array $options @see self::$lexerOptions
+ */
+ public function setLexerOptions($options)
+ {
+ $lexer = new \Twig_Lexer($this->twig, $options);
+ $this->twig->setLexer($lexer);
+ }
+
+ /**
+ * Adds custom function or filter
+ * @param string $classType 'Function' or 'Filter'
+ * @param array $elements Parameters of elements to add
+ * @throws \Exception
+ */
+ private function _addCustom($classType, $elements)
+ {
+ $classFunction = 'Twig_' . $classType . '_Function';
+
+ foreach ($elements as $name => $func) {
+ $twigElement = null;
+
+ switch ($func) {
+ // Just a name of function
+ case is_string($func):
+ $twigElement = new $classFunction($func);
+ break;
+ // Name of function + options array
+ case is_array($func) && is_string($func[0]) && isset($func[1]) && is_array($func[1]):
+ $twigElement = new $classFunction($func[0], $func[1]);
+ break;
+ }
+
+ if ($twigElement !== null) {
+ $this->twig->{'add'.$classType}($name, $twigElement);
+ } else {
+ throw new \Exception("Incorrect options for \"$classType\" $name.");
+ }
+ }
+ }
}
diff --git a/extensions/twig/ViewRendererStaticClassProxy.php b/extensions/twig/ViewRendererStaticClassProxy.php
index 343cdadcbed..e25b430f073 100644
--- a/extensions/twig/ViewRendererStaticClassProxy.php
+++ b/extensions/twig/ViewRendererStaticClassProxy.php
@@ -17,28 +17,30 @@
*/
class ViewRendererStaticClassProxy
{
- private $_staticClassName;
-
- public function __construct($staticClassName)
- {
- $this->_staticClassName = $staticClassName;
- }
-
- public function __get($property)
- {
- $class = new \ReflectionClass($this->_staticClassName);
- return $class->getStaticPropertyValue($property);
- }
-
- public function __set($property, $value)
- {
- $class = new \ReflectionClass($this->_staticClassName);
- $class->setStaticPropertyValue($property, $value);
- return $value;
- }
-
- public function __call($method, $arguments)
- {
- return call_user_func_array([$this->_staticClassName, $method], $arguments);
- }
+ private $_staticClassName;
+
+ public function __construct($staticClassName)
+ {
+ $this->_staticClassName = $staticClassName;
+ }
+
+ public function __get($property)
+ {
+ $class = new \ReflectionClass($this->_staticClassName);
+
+ return $class->getStaticPropertyValue($property);
+ }
+
+ public function __set($property, $value)
+ {
+ $class = new \ReflectionClass($this->_staticClassName);
+ $class->setStaticPropertyValue($property, $value);
+
+ return $value;
+ }
+
+ public function __call($method, $arguments)
+ {
+ return call_user_func_array([$this->_staticClassName, $method], $arguments);
+ }
}
diff --git a/framework/BaseYii.php b/framework/BaseYii.php
index c70808e0bd7..3457b523640 100644
--- a/framework/BaseYii.php
+++ b/framework/BaseYii.php
@@ -46,7 +46,6 @@
*/
defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true);
-
/**
* BaseYii is the core helper class for the Yii framework.
*
@@ -58,477 +57,481 @@
*/
class BaseYii
{
- /**
- * @var array class map used by the Yii autoloading mechanism.
- * The array keys are the class names (without leading backslashes), and the array values
- * are the corresponding class file paths (or path aliases). This property mainly affects
- * how [[autoload()]] works.
- * @see autoload()
- */
- public static $classMap = [];
- /**
- * @var \yii\console\Application|\yii\web\Application the application instance
- */
- public static $app;
- /**
- * @var array registered path aliases
- * @see getAlias()
- * @see setAlias()
- */
- public static $aliases = ['@yii' => __DIR__];
- /**
- * @var array initial property values that will be applied to objects newly created via [[createObject]].
- * The array keys are class names without leading backslashes "\", and the array values are the corresponding
- * name-value pairs for initializing the created class instances. For example,
- *
- * ~~~
- * [
- * 'Bar' => [
- * 'prop1' => 'value1',
- * 'prop2' => 'value2',
- * ],
- * 'mycompany\foo\Car' => [
- * 'prop1' => 'value1',
- * 'prop2' => 'value2',
- * ],
- * ]
- * ~~~
- *
- * @see createObject()
- */
- public static $objectConfig = [];
-
-
- /**
- * Returns a string representing the current version of the Yii framework.
- * @return string the version of Yii framework
- */
- public static function getVersion()
- {
- return '2.0.0-dev';
- }
-
- /**
- * Translates a path alias into an actual path.
- *
- * The translation is done according to the following procedure:
- *
- * 1. If the given alias does not start with '@', it is returned back without change;
- * 2. Otherwise, look for the longest registered alias that matches the beginning part
- * of the given alias. If it exists, replace the matching part of the given alias with
- * the corresponding registered path.
- * 3. Throw an exception or return false, depending on the `$throwException` parameter.
- *
- * For example, by default '@yii' is registered as the alias to the Yii framework directory,
- * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'.
- *
- * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config'
- * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path.
- * This is because the longest alias takes precedence.
- *
- * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced
- * instead of '@foo/bar', because '/' serves as the boundary character.
- *
- * Note, this method does not check if the returned path exists or not.
- *
- * @param string $alias the alias to be translated.
- * @param boolean $throwException whether to throw an exception if the given alias is invalid.
- * If this is false and an invalid alias is given, false will be returned by this method.
- * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered.
- * @throws InvalidParamException if the alias is invalid while $throwException is true.
- * @see setAlias()
- */
- public static function getAlias($alias, $throwException = true)
- {
- if (strncmp($alias, '@', 1)) {
- // not an alias
- return $alias;
- }
-
- $pos = strpos($alias, '/');
- $root = $pos === false ? $alias : substr($alias, 0, $pos);
-
- if (isset(static::$aliases[$root])) {
- if (is_string(static::$aliases[$root])) {
- return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos);
- } else {
- foreach (static::$aliases[$root] as $name => $path) {
- if (strpos($alias . '/', $name . '/') === 0) {
- return $path . substr($alias, strlen($name));
- }
- }
- }
- }
-
- if ($throwException) {
- throw new InvalidParamException("Invalid path alias: $alias");
- } else {
- return false;
- }
- }
-
- /**
- * Returns the root alias part of a given alias.
- * A root alias is an alias that has been registered via [[setAlias()]] previously.
- * If a given alias matches multiple root aliases, the longest one will be returned.
- * @param string $alias the alias
- * @return string|boolean the root alias, or false if no root alias is found
- */
- public static function getRootAlias($alias)
- {
- $pos = strpos($alias, '/');
- $root = $pos === false ? $alias : substr($alias, 0, $pos);
-
- if (isset(static::$aliases[$root])) {
- if (is_string(static::$aliases[$root])) {
- return $root;
- } else {
- foreach (static::$aliases[$root] as $name => $path) {
- if (strpos($alias . '/', $name . '/') === 0) {
- return $name;
- }
- }
- }
- }
- return false;
- }
-
- /**
- * Registers a path alias.
- *
- * A path alias is a short name representing a long path (a file path, a URL, etc.)
- * For example, we use '@yii' as the alias of the path to the Yii framework directory.
- *
- * A path alias must start with the character '@' so that it can be easily differentiated
- * from non-alias paths.
- *
- * Note that this method does not check if the given path exists or not. All it does is
- * to associate the alias with the path.
- *
- * Any trailing '/' and '\' characters in the given path will be trimmed.
- *
- * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character.
- * It may contain the forward slash '/' which serves as boundary character when performing
- * alias translation by [[getAlias()]].
- * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters
- * will be trimmed. This can be
- *
- * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`)
- * - a URL (e.g. `http://www.yiiframework.com`)
- * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the
- * actual path first by calling [[getAlias()]].
- *
- * @throws InvalidParamException if $path is an invalid alias.
- * @see getAlias()
- */
- public static function setAlias($alias, $path)
- {
- if (strncmp($alias, '@', 1)) {
- $alias = '@' . $alias;
- }
- $pos = strpos($alias, '/');
- $root = $pos === false ? $alias : substr($alias, 0, $pos);
- if ($path !== null) {
- $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path);
- if (!isset(static::$aliases[$root])) {
- if ($pos === false) {
- static::$aliases[$root] = $path;
- } else {
- static::$aliases[$root] = [$alias => $path];
- }
- } elseif (is_string(static::$aliases[$root])) {
- if ($pos === false) {
- static::$aliases[$root] = $path;
- } else {
- static::$aliases[$root] = [
- $alias => $path,
- $root => static::$aliases[$root],
- ];
- }
- } else {
- static::$aliases[$root][$alias] = $path;
- krsort(static::$aliases[$root]);
- }
- } elseif (isset(static::$aliases[$root])) {
- if (is_array(static::$aliases[$root])) {
- unset(static::$aliases[$root][$alias]);
- } elseif ($pos === false) {
- unset(static::$aliases[$root]);
- }
- }
- }
-
- /**
- * Class autoload loader.
- * This method is invoked automatically when PHP sees an unknown class.
- * The method will attempt to include the class file according to the following procedure:
- *
- * 1. Search in [[classMap]];
- * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt
- * to include the file associated with the corresponding path alias
- * (e.g. `@yii/base/Component.php`);
- *
- * This autoloader allows loading classes that follow the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/)
- * and have its top-level namespace or sub-namespaces defined as path aliases.
- *
- * Example: When aliases `@yii` and `@yii/bootstrap` are defined, classes in the `yii\bootstrap` namespace
- * will be loaded using the `@yii/bootstrap` alias which points to the directory where bootstrap extension
- * files are installed and all classes from other `yii` namespaces will be loaded from the yii framework directory.
- *
- * @param string $className the fully qualified class name without a leading backslash "\"
- * @throws UnknownClassException if the class does not exist in the class file
- */
- public static function autoload($className)
- {
- if (isset(static::$classMap[$className])) {
- $classFile = static::$classMap[$className];
- if ($classFile[0] === '@') {
- $classFile = static::getAlias($classFile);
- }
- } elseif (strpos($className, '\\') !== false) {
- $classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false);
- if ($classFile === false || !is_file($classFile)) {
- return;
- }
- } else {
- return;
- }
-
- include($classFile);
-
- if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) {
- throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?");
- }
- }
-
- /**
- * Creates a new object using the given configuration.
- *
- * The configuration can be either a string or an array.
- * If a string, it is treated as the *object class*; if an array,
- * it must contain a `class` element specifying the *object class*, and
- * the rest of the name-value pairs in the array will be used to initialize
- * the corresponding object properties.
- *
- * Below are some usage examples:
- *
- * ~~~
- * $object = \Yii::createObject('app\components\GoogleMap');
- * $object = \Yii::createObject([
- * 'class' => 'app\components\GoogleMap',
- * 'apiKey' => 'xyz',
- * ]);
- * ~~~
- *
- * This method can be used to create any object as long as the object's constructor is
- * defined like the following:
- *
- * ~~~
- * public function __construct(..., $config = []) {
- * }
- * ~~~
- *
- * The method will pass the given configuration as the last parameter of the constructor,
- * and any additional parameters to this method will be passed as the rest of the constructor parameters.
- *
- * @param string|array $config the configuration. It can be either a string representing the class name
- * or an array representing the object configuration.
- * @return mixed the created object
- * @throws InvalidConfigException if the configuration is invalid.
- */
- public static function createObject($config)
- {
- static $reflections = [];
-
- if (is_string($config)) {
- $class = $config;
- $config = [];
- } elseif (isset($config['class'])) {
- $class = $config['class'];
- unset($config['class']);
- } else {
- throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
- }
-
- $class = ltrim($class, '\\');
-
- if (isset(static::$objectConfig[$class])) {
- $config = array_merge(static::$objectConfig[$class], $config);
- }
-
- if (func_num_args() > 1) {
- /** @var \ReflectionClass $reflection */
- if (isset($reflections[$class])) {
- $reflection = $reflections[$class];
- } else {
- $reflection = $reflections[$class] = new \ReflectionClass($class);
- }
- $args = func_get_args();
- array_shift($args); // remove $config
- if (!empty($config)) {
- $args[] = $config;
- }
- return $reflection->newInstanceArgs($args);
- } else {
- return empty($config) ? new $class : new $class($config);
- }
- }
-
- /**
- * Logs a trace message.
- * Trace messages are logged mainly for development purpose to see
- * the execution work flow of some code.
- * @param string $message the message to be logged.
- * @param string $category the category of the message.
- */
- public static function trace($message, $category = 'application')
- {
- if (YII_DEBUG) {
- static::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category);
- }
- }
-
- /**
- * Logs an error message.
- * An error message is typically logged when an unrecoverable error occurs
- * during the execution of an application.
- * @param string $message the message to be logged.
- * @param string $category the category of the message.
- */
- public static function error($message, $category = 'application')
- {
- static::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category);
- }
-
- /**
- * Logs a warning message.
- * A warning message is typically logged when an error occurs while the execution
- * can still continue.
- * @param string $message the message to be logged.
- * @param string $category the category of the message.
- */
- public static function warning($message, $category = 'application')
- {
- static::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category);
- }
-
- /**
- * Logs an informative message.
- * An informative message is typically logged by an application to keep record of
- * something important (e.g. an administrator logs in).
- * @param string $message the message to be logged.
- * @param string $category the category of the message.
- */
- public static function info($message, $category = 'application')
- {
- static::$app->getLog()->log($message, Logger::LEVEL_INFO, $category);
- }
-
- /**
- * Marks the beginning of a code block for profiling.
- * This has to be matched with a call to [[endProfile]] with the same category name.
- * The begin- and end- calls must also be properly nested. For example,
- *
- * ~~~
- * \Yii::beginProfile('block1');
- * // some code to be profiled
- * \Yii::beginProfile('block2');
- * // some other code to be profiled
- * \Yii::endProfile('block2');
- * \Yii::endProfile('block1');
- * ~~~
- * @param string $token token for the code block
- * @param string $category the category of this log message
- * @see endProfile()
- */
- public static function beginProfile($token, $category = 'application')
- {
- static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
- }
-
- /**
- * Marks the end of a code block for profiling.
- * This has to be matched with a previous call to [[beginProfile]] with the same category name.
- * @param string $token token for the code block
- * @param string $category the category of this log message
- * @see beginProfile()
- */
- public static function endProfile($token, $category = 'application')
- {
- static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category);
- }
-
- /**
- * Returns an HTML hyperlink that can be displayed on your Web page showing "Powered by Yii Framework" information.
- * @return string an HTML hyperlink that can be displayed on your Web page showing "Powered by Yii Framework" information
- */
- public static function powered()
- {
- return 'Powered by Yii Framework';
- }
-
- /**
- * Translates a message to the specified language.
- *
- * This is a shortcut method of [[\yii\i18n\I18N::translate()]].
- *
- * The translation will be conducted according to the message category and the target language will be used.
- *
- * You can add parameters to a translation message that will be substituted with the corresponding value after
- * translation. The format for this is to use curly brackets around the parameter name as you can see in the following example:
- *
- * ```php
- * $username = 'Alexander';
- * echo \Yii::t('app', 'Hello, {username}!', ['username' => $username]);
- * ```
- *
- * Further formatting of message parameters is supported using the [PHP intl extensions](http://www.php.net/manual/en/intro.intl.php)
- * message formatter. See [[\yii\i18n\I18N::translate()]] for more details.
- *
- * @param string $category the message category.
- * @param string $message the message to be translated.
- * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
- * @param string $language the language code (e.g. `en-US`, `en`). If this is null, the current
- * [[\yii\base\Application::language|application language]] will be used.
- * @return string the translated message.
- */
- public static function t($category, $message, $params = [], $language = null)
- {
- if (static::$app !== null) {
- return static::$app->getI18n()->translate($category, $message, $params, $language ?: static::$app->language);
- } else {
- $p = [];
- foreach ((array) $params as $name => $value) {
- $p['{' . $name . '}'] = $value;
- }
- return ($p === []) ? $message : strtr($message, $p);
- }
- }
-
- /**
- * Configures an object with the initial property values.
- * @param object $object the object to be configured
- * @param array $properties the property initial values given in terms of name-value pairs.
- * @return object the object itself
- */
- public static function configure($object, $properties)
- {
- foreach ($properties as $name => $value) {
- $object->$name = $value;
- }
- return $object;
- }
-
- /**
- * Returns the public member variables of an object.
- * This method is provided such that we can get the public member variables of an object.
- * It is different from "get_object_vars()" because the latter will return private
- * and protected variables if it is called within the object itself.
- * @param object $object the object to be handled
- * @return array the public member variables of the object
- */
- public static function getObjectVars($object)
- {
- return get_object_vars($object);
- }
+ /**
+ * @var array class map used by the Yii autoloading mechanism.
+ * The array keys are the class names (without leading backslashes), and the array values
+ * are the corresponding class file paths (or path aliases). This property mainly affects
+ * how [[autoload()]] works.
+ * @see autoload()
+ */
+ public static $classMap = [];
+ /**
+ * @var \yii\console\Application|\yii\web\Application the application instance
+ */
+ public static $app;
+ /**
+ * @var array registered path aliases
+ * @see getAlias()
+ * @see setAlias()
+ */
+ public static $aliases = ['@yii' => __DIR__];
+ /**
+ * @var array initial property values that will be applied to objects newly created via [[createObject]].
+ * The array keys are class names without leading backslashes "\", and the array values are the corresponding
+ * name-value pairs for initializing the created class instances. For example,
+ *
+ * ~~~
+ * [
+ * 'Bar' => [
+ * 'prop1' => 'value1',
+ * 'prop2' => 'value2',
+ * ],
+ * 'mycompany\foo\Car' => [
+ * 'prop1' => 'value1',
+ * 'prop2' => 'value2',
+ * ],
+ * ]
+ * ~~~
+ *
+ * @see createObject()
+ */
+ public static $objectConfig = [];
+
+
+ /**
+ * Returns a string representing the current version of the Yii framework.
+ * @return string the version of Yii framework
+ */
+ public static function getVersion()
+ {
+ return '2.0.0-dev';
+ }
+
+ /**
+ * Translates a path alias into an actual path.
+ *
+ * The translation is done according to the following procedure:
+ *
+ * 1. If the given alias does not start with '@', it is returned back without change;
+ * 2. Otherwise, look for the longest registered alias that matches the beginning part
+ * of the given alias. If it exists, replace the matching part of the given alias with
+ * the corresponding registered path.
+ * 3. Throw an exception or return false, depending on the `$throwException` parameter.
+ *
+ * For example, by default '@yii' is registered as the alias to the Yii framework directory,
+ * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'.
+ *
+ * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config'
+ * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path.
+ * This is because the longest alias takes precedence.
+ *
+ * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced
+ * instead of '@foo/bar', because '/' serves as the boundary character.
+ *
+ * Note, this method does not check if the returned path exists or not.
+ *
+ * @param string $alias the alias to be translated.
+ * @param boolean $throwException whether to throw an exception if the given alias is invalid.
+ * If this is false and an invalid alias is given, false will be returned by this method.
+ * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered.
+ * @throws InvalidParamException if the alias is invalid while $throwException is true.
+ * @see setAlias()
+ */
+ public static function getAlias($alias, $throwException = true)
+ {
+ if (strncmp($alias, '@', 1)) {
+ // not an alias
+ return $alias;
+ }
+
+ $pos = strpos($alias, '/');
+ $root = $pos === false ? $alias : substr($alias, 0, $pos);
+
+ if (isset(static::$aliases[$root])) {
+ if (is_string(static::$aliases[$root])) {
+ return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos);
+ } else {
+ foreach (static::$aliases[$root] as $name => $path) {
+ if (strpos($alias . '/', $name . '/') === 0) {
+ return $path . substr($alias, strlen($name));
+ }
+ }
+ }
+ }
+
+ if ($throwException) {
+ throw new InvalidParamException("Invalid path alias: $alias");
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the root alias part of a given alias.
+ * A root alias is an alias that has been registered via [[setAlias()]] previously.
+ * If a given alias matches multiple root aliases, the longest one will be returned.
+ * @param string $alias the alias
+ * @return string|boolean the root alias, or false if no root alias is found
+ */
+ public static function getRootAlias($alias)
+ {
+ $pos = strpos($alias, '/');
+ $root = $pos === false ? $alias : substr($alias, 0, $pos);
+
+ if (isset(static::$aliases[$root])) {
+ if (is_string(static::$aliases[$root])) {
+ return $root;
+ } else {
+ foreach (static::$aliases[$root] as $name => $path) {
+ if (strpos($alias . '/', $name . '/') === 0) {
+ return $name;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Registers a path alias.
+ *
+ * A path alias is a short name representing a long path (a file path, a URL, etc.)
+ * For example, we use '@yii' as the alias of the path to the Yii framework directory.
+ *
+ * A path alias must start with the character '@' so that it can be easily differentiated
+ * from non-alias paths.
+ *
+ * Note that this method does not check if the given path exists or not. All it does is
+ * to associate the alias with the path.
+ *
+ * Any trailing '/' and '\' characters in the given path will be trimmed.
+ *
+ * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character.
+ * It may contain the forward slash '/' which serves as boundary character when performing
+ * alias translation by [[getAlias()]].
+ * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters
+ * will be trimmed. This can be
+ *
+ * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`)
+ * - a URL (e.g. `http://www.yiiframework.com`)
+ * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the
+ * actual path first by calling [[getAlias()]].
+ *
+ * @throws InvalidParamException if $path is an invalid alias.
+ * @see getAlias()
+ */
+ public static function setAlias($alias, $path)
+ {
+ if (strncmp($alias, '@', 1)) {
+ $alias = '@' . $alias;
+ }
+ $pos = strpos($alias, '/');
+ $root = $pos === false ? $alias : substr($alias, 0, $pos);
+ if ($path !== null) {
+ $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path);
+ if (!isset(static::$aliases[$root])) {
+ if ($pos === false) {
+ static::$aliases[$root] = $path;
+ } else {
+ static::$aliases[$root] = [$alias => $path];
+ }
+ } elseif (is_string(static::$aliases[$root])) {
+ if ($pos === false) {
+ static::$aliases[$root] = $path;
+ } else {
+ static::$aliases[$root] = [
+ $alias => $path,
+ $root => static::$aliases[$root],
+ ];
+ }
+ } else {
+ static::$aliases[$root][$alias] = $path;
+ krsort(static::$aliases[$root]);
+ }
+ } elseif (isset(static::$aliases[$root])) {
+ if (is_array(static::$aliases[$root])) {
+ unset(static::$aliases[$root][$alias]);
+ } elseif ($pos === false) {
+ unset(static::$aliases[$root]);
+ }
+ }
+ }
+
+ /**
+ * Class autoload loader.
+ * This method is invoked automatically when PHP sees an unknown class.
+ * The method will attempt to include the class file according to the following procedure:
+ *
+ * 1. Search in [[classMap]];
+ * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt
+ * to include the file associated with the corresponding path alias
+ * (e.g. `@yii/base/Component.php`);
+ *
+ * This autoloader allows loading classes that follow the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/)
+ * and have its top-level namespace or sub-namespaces defined as path aliases.
+ *
+ * Example: When aliases `@yii` and `@yii/bootstrap` are defined, classes in the `yii\bootstrap` namespace
+ * will be loaded using the `@yii/bootstrap` alias which points to the directory where bootstrap extension
+ * files are installed and all classes from other `yii` namespaces will be loaded from the yii framework directory.
+ *
+ * @param string $className the fully qualified class name without a leading backslash "\"
+ * @throws UnknownClassException if the class does not exist in the class file
+ */
+ public static function autoload($className)
+ {
+ if (isset(static::$classMap[$className])) {
+ $classFile = static::$classMap[$className];
+ if ($classFile[0] === '@') {
+ $classFile = static::getAlias($classFile);
+ }
+ } elseif (strpos($className, '\\') !== false) {
+ $classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false);
+ if ($classFile === false || !is_file($classFile)) {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ include($classFile);
+
+ if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) {
+ throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?");
+ }
+ }
+
+ /**
+ * Creates a new object using the given configuration.
+ *
+ * The configuration can be either a string or an array.
+ * If a string, it is treated as the *object class*; if an array,
+ * it must contain a `class` element specifying the *object class*, and
+ * the rest of the name-value pairs in the array will be used to initialize
+ * the corresponding object properties.
+ *
+ * Below are some usage examples:
+ *
+ * ~~~
+ * $object = \Yii::createObject('app\components\GoogleMap');
+ * $object = \Yii::createObject([
+ * 'class' => 'app\components\GoogleMap',
+ * 'apiKey' => 'xyz',
+ * ]);
+ * ~~~
+ *
+ * This method can be used to create any object as long as the object's constructor is
+ * defined like the following:
+ *
+ * ~~~
+ * public function __construct(..., $config = []) {
+ * }
+ * ~~~
+ *
+ * The method will pass the given configuration as the last parameter of the constructor,
+ * and any additional parameters to this method will be passed as the rest of the constructor parameters.
+ *
+ * @param string|array $config the configuration. It can be either a string representing the class name
+ * or an array representing the object configuration.
+ * @return mixed the created object
+ * @throws InvalidConfigException if the configuration is invalid.
+ */
+ public static function createObject($config)
+ {
+ static $reflections = [];
+
+ if (is_string($config)) {
+ $class = $config;
+ $config = [];
+ } elseif (isset($config['class'])) {
+ $class = $config['class'];
+ unset($config['class']);
+ } else {
+ throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
+ }
+
+ $class = ltrim($class, '\\');
+
+ if (isset(static::$objectConfig[$class])) {
+ $config = array_merge(static::$objectConfig[$class], $config);
+ }
+
+ if (func_num_args() > 1) {
+ /** @var \ReflectionClass $reflection */
+ if (isset($reflections[$class])) {
+ $reflection = $reflections[$class];
+ } else {
+ $reflection = $reflections[$class] = new \ReflectionClass($class);
+ }
+ $args = func_get_args();
+ array_shift($args); // remove $config
+ if (!empty($config)) {
+ $args[] = $config;
+ }
+
+ return $reflection->newInstanceArgs($args);
+ } else {
+ return empty($config) ? new $class : new $class($config);
+ }
+ }
+
+ /**
+ * Logs a trace message.
+ * Trace messages are logged mainly for development purpose to see
+ * the execution work flow of some code.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public static function trace($message, $category = 'application')
+ {
+ if (YII_DEBUG) {
+ static::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category);
+ }
+ }
+
+ /**
+ * Logs an error message.
+ * An error message is typically logged when an unrecoverable error occurs
+ * during the execution of an application.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public static function error($message, $category = 'application')
+ {
+ static::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category);
+ }
+
+ /**
+ * Logs a warning message.
+ * A warning message is typically logged when an error occurs while the execution
+ * can still continue.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public static function warning($message, $category = 'application')
+ {
+ static::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category);
+ }
+
+ /**
+ * Logs an informative message.
+ * An informative message is typically logged by an application to keep record of
+ * something important (e.g. an administrator logs in).
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public static function info($message, $category = 'application')
+ {
+ static::$app->getLog()->log($message, Logger::LEVEL_INFO, $category);
+ }
+
+ /**
+ * Marks the beginning of a code block for profiling.
+ * This has to be matched with a call to [[endProfile]] with the same category name.
+ * The begin- and end- calls must also be properly nested. For example,
+ *
+ * ~~~
+ * \Yii::beginProfile('block1');
+ * // some code to be profiled
+ * \Yii::beginProfile('block2');
+ * // some other code to be profiled
+ * \Yii::endProfile('block2');
+ * \Yii::endProfile('block1');
+ * ~~~
+ * @param string $token token for the code block
+ * @param string $category the category of this log message
+ * @see endProfile()
+ */
+ public static function beginProfile($token, $category = 'application')
+ {
+ static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
+ }
+
+ /**
+ * Marks the end of a code block for profiling.
+ * This has to be matched with a previous call to [[beginProfile]] with the same category name.
+ * @param string $token token for the code block
+ * @param string $category the category of this log message
+ * @see beginProfile()
+ */
+ public static function endProfile($token, $category = 'application')
+ {
+ static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category);
+ }
+
+ /**
+ * Returns an HTML hyperlink that can be displayed on your Web page showing "Powered by Yii Framework" information.
+ * @return string an HTML hyperlink that can be displayed on your Web page showing "Powered by Yii Framework" information
+ */
+ public static function powered()
+ {
+ return 'Powered by Yii Framework';
+ }
+
+ /**
+ * Translates a message to the specified language.
+ *
+ * This is a shortcut method of [[\yii\i18n\I18N::translate()]].
+ *
+ * The translation will be conducted according to the message category and the target language will be used.
+ *
+ * You can add parameters to a translation message that will be substituted with the corresponding value after
+ * translation. The format for this is to use curly brackets around the parameter name as you can see in the following example:
+ *
+ * ```php
+ * $username = 'Alexander';
+ * echo \Yii::t('app', 'Hello, {username}!', ['username' => $username]);
+ * ```
+ *
+ * Further formatting of message parameters is supported using the [PHP intl extensions](http://www.php.net/manual/en/intro.intl.php)
+ * message formatter. See [[\yii\i18n\I18N::translate()]] for more details.
+ *
+ * @param string $category the message category.
+ * @param string $message the message to be translated.
+ * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
+ * @param string $language the language code (e.g. `en-US`, `en`). If this is null, the current
+ * [[\yii\base\Application::language|application language]] will be used.
+ * @return string the translated message.
+ */
+ public static function t($category, $message, $params = [], $language = null)
+ {
+ if (static::$app !== null) {
+ return static::$app->getI18n()->translate($category, $message, $params, $language ?: static::$app->language);
+ } else {
+ $p = [];
+ foreach ((array) $params as $name => $value) {
+ $p['{' . $name . '}'] = $value;
+ }
+
+ return ($p === []) ? $message : strtr($message, $p);
+ }
+ }
+
+ /**
+ * Configures an object with the initial property values.
+ * @param object $object the object to be configured
+ * @param array $properties the property initial values given in terms of name-value pairs.
+ * @return object the object itself
+ */
+ public static function configure($object, $properties)
+ {
+ foreach ($properties as $name => $value) {
+ $object->$name = $value;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Returns the public member variables of an object.
+ * This method is provided such that we can get the public member variables of an object.
+ * It is different from "get_object_vars()" because the latter will return private
+ * and protected variables if it is called within the object itself.
+ * @param object $object the object to be handled
+ * @return array the public member variables of the object
+ */
+ public static function getObjectVars($object)
+ {
+ return get_object_vars($object);
+ }
}
diff --git a/framework/base/Action.php b/framework/base/Action.php
index fd9c3613cd3..4299d0a2941 100644
--- a/framework/base/Action.php
+++ b/framework/base/Action.php
@@ -36,79 +36,80 @@
*/
class Action extends Component
{
- /**
- * @var string ID of the action
- */
- public $id;
- /**
- * @var Controller the controller that owns this action
- */
- public $controller;
+ /**
+ * @var string ID of the action
+ */
+ public $id;
+ /**
+ * @var Controller the controller that owns this action
+ */
+ public $controller;
- /**
- * Constructor.
- * @param string $id the ID of this action
- * @param Controller $controller the controller that owns this action
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($id, $controller, $config = [])
- {
- $this->id = $id;
- $this->controller = $controller;
- parent::__construct($config);
- }
+ /**
+ * Constructor.
+ * @param string $id the ID of this action
+ * @param Controller $controller the controller that owns this action
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $controller, $config = [])
+ {
+ $this->id = $id;
+ $this->controller = $controller;
+ parent::__construct($config);
+ }
- /**
- * Returns the unique ID of this action among the whole application.
- * @return string the unique ID of this action among the whole application.
- */
- public function getUniqueId()
- {
- return $this->controller->getUniqueId() . '/' . $this->id;
- }
+ /**
+ * Returns the unique ID of this action among the whole application.
+ * @return string the unique ID of this action among the whole application.
+ */
+ public function getUniqueId()
+ {
+ return $this->controller->getUniqueId() . '/' . $this->id;
+ }
- /**
- * Runs this action with the specified parameters.
- * This method is mainly invoked by the controller.
- * @param array $params the parameters to be bound to the action's run() method.
- * @return mixed the result of the action
- * @throws InvalidConfigException if the action class does not have a run() method
- */
- public function runWithParams($params)
- {
- if (!method_exists($this, 'run')) {
- throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
- }
- $args = $this->controller->bindActionParams($this, $params);
- Yii::trace('Running action: ' . get_class($this) . '::run()', __METHOD__);
- if (Yii::$app->requestedParams === null) {
- Yii::$app->requestedParams = $args;
- }
- if ($this->beforeRun()) {
- $result = call_user_func_array([$this, 'run'], $args);
- $this->afterRun();
- return $result;
- } else {
- return null;
- }
- }
+ /**
+ * Runs this action with the specified parameters.
+ * This method is mainly invoked by the controller.
+ * @param array $params the parameters to be bound to the action's run() method.
+ * @return mixed the result of the action
+ * @throws InvalidConfigException if the action class does not have a run() method
+ */
+ public function runWithParams($params)
+ {
+ if (!method_exists($this, 'run')) {
+ throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
+ }
+ $args = $this->controller->bindActionParams($this, $params);
+ Yii::trace('Running action: ' . get_class($this) . '::run()', __METHOD__);
+ if (Yii::$app->requestedParams === null) {
+ Yii::$app->requestedParams = $args;
+ }
+ if ($this->beforeRun()) {
+ $result = call_user_func_array([$this, 'run'], $args);
+ $this->afterRun();
- /**
- * This method is called right before `run()` is executed.
- * You may override this method to do preparation work for the action run.
- * If the method returns false, it will cancel the action.
- * @return boolean whether to run the action.
- */
- protected function beforeRun()
- {
- return true;
- }
+ return $result;
+ } else {
+ return null;
+ }
+ }
- /**
- * This method is called right after `run()` is executed.
- * You may override this method to do post-processing work for the action run.
- */
- protected function afterRun()
- {
- }
+ /**
+ * This method is called right before `run()` is executed.
+ * You may override this method to do preparation work for the action run.
+ * If the method returns false, it will cancel the action.
+ * @return boolean whether to run the action.
+ */
+ protected function beforeRun()
+ {
+ return true;
+ }
+
+ /**
+ * This method is called right after `run()` is executed.
+ * You may override this method to do post-processing work for the action run.
+ */
+ protected function afterRun()
+ {
+ }
}
diff --git a/framework/base/ActionEvent.php b/framework/base/ActionEvent.php
index 6e123a009e9..7c76b918c92 100644
--- a/framework/base/ActionEvent.php
+++ b/framework/base/ActionEvent.php
@@ -17,29 +17,29 @@
*/
class ActionEvent extends Event
{
- /**
- * @var Action the action currently being executed
- */
- public $action;
- /**
- * @var mixed the action result. Event handlers may modify this property to change the action result.
- */
- public $result;
- /**
- * @var boolean whether to continue running the action. Event handlers of
- * [[Controller::EVENT_BEFORE_ACTION]] may set this property to decide whether
- * to continue running the current action.
- */
- public $isValid = true;
+ /**
+ * @var Action the action currently being executed
+ */
+ public $action;
+ /**
+ * @var mixed the action result. Event handlers may modify this property to change the action result.
+ */
+ public $result;
+ /**
+ * @var boolean whether to continue running the action. Event handlers of
+ * [[Controller::EVENT_BEFORE_ACTION]] may set this property to decide whether
+ * to continue running the current action.
+ */
+ public $isValid = true;
- /**
- * Constructor.
- * @param Action $action the action associated with this action event.
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($action, $config = [])
- {
- $this->action = $action;
- parent::__construct($config);
- }
+ /**
+ * Constructor.
+ * @param Action $action the action associated with this action event.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($action, $config = [])
+ {
+ $this->action = $action;
+ parent::__construct($config);
+ }
}
diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php
index 6fb114ca037..4bb2cf441cb 100644
--- a/framework/base/ActionFilter.php
+++ b/framework/base/ActionFilter.php
@@ -18,87 +18,88 @@
*/
class ActionFilter extends Behavior
{
- /**
- * @var array list of action IDs that this filter should apply to. If this property is not set,
- * then the filter applies to all actions, unless they are listed in [[except]].
- * If an action ID appears in both [[only]] and [[except]], this filter will NOT apply to it.
- * @see except
- */
- public $only;
- /**
- * @var array list of action IDs that this filter should not apply to.
- * @see only
- */
- public $except = [];
+ /**
+ * @var array list of action IDs that this filter should apply to. If this property is not set,
+ * then the filter applies to all actions, unless they are listed in [[except]].
+ * If an action ID appears in both [[only]] and [[except]], this filter will NOT apply to it.
+ * @see except
+ */
+ public $only;
+ /**
+ * @var array list of action IDs that this filter should not apply to.
+ * @see only
+ */
+ public $except = [];
- /**
- * Declares event handlers for the [[owner]]'s events.
- * @return array events (array keys) and the corresponding event handler methods (array values).
- */
- public function events()
- {
- return [
- Controller::EVENT_BEFORE_ACTION => 'beforeFilter',
- Controller::EVENT_AFTER_ACTION => 'afterFilter',
- ];
- }
+ /**
+ * Declares event handlers for the [[owner]]'s events.
+ * @return array events (array keys) and the corresponding event handler methods (array values).
+ */
+ public function events()
+ {
+ return [
+ Controller::EVENT_BEFORE_ACTION => 'beforeFilter',
+ Controller::EVENT_AFTER_ACTION => 'afterFilter',
+ ];
+ }
- /**
- * @param ActionEvent $event
- * @return boolean
- */
- public function beforeFilter($event)
- {
- if ($this->isActive($event->action)) {
- $event->isValid = $this->beforeAction($event->action);
- if (!$event->isValid) {
- $event->handled = true;
- }
- }
- return $event->isValid;
- }
+ /**
+ * @param ActionEvent $event
+ * @return boolean
+ */
+ public function beforeFilter($event)
+ {
+ if ($this->isActive($event->action)) {
+ $event->isValid = $this->beforeAction($event->action);
+ if (!$event->isValid) {
+ $event->handled = true;
+ }
+ }
- /**
- * @param ActionEvent $event
- * @return boolean
- */
- public function afterFilter($event)
- {
- if ($this->isActive($event->action)) {
- $event->result = $this->afterAction($event->action, $event->result);
- }
- }
+ return $event->isValid;
+ }
- /**
- * This method is invoked right before an action is to be executed (after all possible filters.)
- * You may override this method to do last-minute preparation for the action.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- return true;
- }
+ /**
+ * @param ActionEvent $event
+ * @return boolean
+ */
+ public function afterFilter($event)
+ {
+ if ($this->isActive($event->action)) {
+ $event->result = $this->afterAction($event->action, $event->result);
+ }
+ }
- /**
- * This method is invoked right after an action is executed.
- * You may override this method to do some postprocessing for the action.
- * @param Action $action the action just executed.
- * @param mixed $result the action execution result
- * @return mixed the processed action result.
- */
- public function afterAction($action, $result)
- {
- return $result;
- }
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ return true;
+ }
- /**
- * Returns a value indicating whether the filer is active for the given action.
- * @param Action $action the action being filtered
- * @return boolean whether the filer is active for the given action.
- */
- protected function isActive($action)
- {
- return !in_array($action->id, $this->except, true) && (empty($this->only) || in_array($action->id, $this->only, true));
- }
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * @param Action $action the action just executed.
+ * @param mixed $result the action execution result
+ * @return mixed the processed action result.
+ */
+ public function afterAction($action, $result)
+ {
+ return $result;
+ }
+
+ /**
+ * Returns a value indicating whether the filer is active for the given action.
+ * @param Action $action the action being filtered
+ * @return boolean whether the filer is active for the given action.
+ */
+ protected function isActive($action)
+ {
+ return !in_array($action->id, $this->except, true) && (empty($this->only) || in_array($action->id, $this->only, true));
+ }
}
diff --git a/framework/base/Application.php b/framework/base/Application.php
index ffdc4a7b7bf..33efe2c657d 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -40,610 +40,612 @@
*/
abstract class Application extends Module
{
- /**
- * @event Event an event raised before the application starts to handle a request.
- */
- const EVENT_BEFORE_REQUEST = 'beforeRequest';
- /**
- * @event Event an event raised after the application successfully handles a request (before the response is sent out).
- */
- const EVENT_AFTER_REQUEST = 'afterRequest';
- /**
- * @event ActionEvent an event raised before executing a controller action.
- * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
- */
- const EVENT_BEFORE_ACTION = 'beforeAction';
- /**
- * @event ActionEvent an event raised after executing a controller action.
- */
- const EVENT_AFTER_ACTION = 'afterAction';
-
- /**
- * @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.
- */
- public $name = 'My Application';
- /**
- * @var string the version of this application.
- */
- public $version = '1.0';
- /**
- * @var string the charset currently used for the application.
- */
- public $charset = 'UTF-8';
- /**
- * @var string the language that is meant to be used for end users.
- * @see sourceLanguage
- */
- public $language = 'en';
- /**
- * @var string the language that the application is written in. This mainly refers to
- * the language that the messages and view files are written in.
- * @see language
- */
- public $sourceLanguage = 'en';
- /**
- * @var Controller the currently active controller instance
- */
- public $controller;
- /**
- * @var string|boolean the layout that should be applied for views in this application. Defaults to 'main'.
- * If this is false, layout will be disabled.
- */
- public $layout = 'main';
- /**
- * @var integer the size of the reserved memory. A portion of memory is pre-allocated so that
- * when an out-of-memory issue occurs, the error handler is able to handle the error with
- * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
- * Defaults to 256KB.
- */
- public $memoryReserveSize = 262144;
- /**
- * @var string the requested route
- */
- public $requestedRoute;
- /**
- * @var Action the requested Action. If null, it means the request cannot be resolved into an action.
- */
- public $requestedAction;
- /**
- * @var array the parameters supplied to the requested action.
- */
- public $requestedParams;
- /**
- * @var array list of installed Yii extensions. Each array element represents a single extension
- * with the following structure:
- *
- * ~~~
- * [
- * 'name' => 'extension name',
- * 'version' => 'version number',
- * 'bootstrap' => 'BootstrapClassName',
- * ]
- * ~~~
- */
- public $extensions = [];
- /**
- * @var \Exception the exception that is being handled currently. When this is not null,
- * it means the application is handling some exception and extra care should be taken.
- */
- public $exception;
-
- /**
- * @var string Used to reserve memory for fatal error handler.
- */
- private $_memoryReserve;
-
- /**
- * Constructor.
- * @param array $config name-value pairs that will be used to initialize the object properties.
- * Note that the configuration must contain both [[id]] and [[basePath]].
- * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
- */
- public function __construct($config = [])
- {
- Yii::$app = $this;
-
- $this->preInit($config);
- $this->registerErrorHandlers();
- $this->registerCoreComponents();
-
- Component::__construct($config);
- }
-
- /**
- * Pre-initializes the application.
- * This method is called at the beginning of the application constructor.
- * It initializes several important application properties.
- * If you override this method, please make sure you call the parent implementation.
- * @param array $config the application configuration
- * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
- */
- public function preInit(&$config)
- {
- if (!isset($config['id'])) {
- throw new InvalidConfigException('The "id" configuration is required.');
- }
- if (isset($config['basePath'])) {
- $this->setBasePath($config['basePath']);
- unset($config['basePath']);
- } else {
- throw new InvalidConfigException('The "basePath" configuration is required.');
- }
-
- if (isset($config['vendorPath'])) {
- $this->setVendorPath($config['vendorPath']);
- unset($config['vendorPath']);
- } else {
- // set "@vendor"
- $this->getVendorPath();
- }
- if (isset($config['runtimePath'])) {
- $this->setRuntimePath($config['runtimePath']);
- unset($config['runtimePath']);
- } else {
- // set "@runtime"
- $this->getRuntimePath();
- }
-
- if (isset($config['timeZone'])) {
- $this->setTimeZone($config['timeZone']);
- unset($config['timeZone']);
- } elseif (!ini_get('date.timezone')) {
- $this->setTimeZone('UTC');
- }
- }
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- $this->initExtensions($this->extensions);
- parent::init();
- }
-
- /**
- * Initializes the extensions.
- * @param array $extensions the extensions to be initialized. Please refer to [[extensions]]
- * for the structure of the extension array.
- */
- protected function initExtensions($extensions)
- {
- foreach ($extensions as $extension) {
- if (!empty($extension['alias'])) {
- foreach ($extension['alias'] as $name => $path) {
- Yii::setAlias($name, $path);
- }
- }
- if (isset($extension['bootstrap'])) {
- /** @var Extension $class */
- $class = $extension['bootstrap'];
- $class::init();
- }
- }
- }
-
- /**
- * Loads components that are declared in [[preload]].
- * @throws InvalidConfigException if a component or module to be preloaded is unknown
- */
- public function preloadComponents()
- {
- $this->getComponent('log');
- parent::preloadComponents();
- }
-
- /**
- * Registers error handlers.
- */
- public function registerErrorHandlers()
- {
- if (YII_ENABLE_ERROR_HANDLER) {
- ini_set('display_errors', 0);
- set_exception_handler([$this, 'handleException']);
- set_error_handler([$this, 'handleError']);
- if ($this->memoryReserveSize > 0) {
- $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
- }
- register_shutdown_function([$this, 'handleFatalError']);
- }
- }
-
- /**
- * Returns an ID that uniquely identifies this module among all modules within the current application.
- * Since this is an application instance, it will always return an empty string.
- * @return string the unique ID of the module.
- */
- public function getUniqueId()
- {
- return '';
- }
-
- /**
- * Sets the root directory of the application and the @app alias.
- * This method can only be invoked at the beginning of the constructor.
- * @param string $path the root directory of the application.
- * @property string the root directory of the application.
- * @throws InvalidParamException if the directory does not exist.
- */
- public function setBasePath($path)
- {
- parent::setBasePath($path);
- Yii::setAlias('@app', $this->getBasePath());
- }
-
- /**
- * Runs the application.
- * This is the main entrance of an application.
- * @return integer the exit status (0 means normal, non-zero values mean abnormal)
- */
- public function run()
- {
- $this->trigger(self::EVENT_BEFORE_REQUEST);
- $response = $this->handleRequest($this->getRequest());
- $this->trigger(self::EVENT_AFTER_REQUEST);
- $response->send();
- return $response->exitStatus;
- }
-
- /**
- * Handles the specified request.
- *
- * This method should return an instance of [[Response]] or its child class
- * which represents the handling result of the request.
- *
- * @param Request $request the request to be handled
- * @return Response the resulting response
- */
- abstract public function handleRequest($request);
-
-
- private $_runtimePath;
-
- /**
- * Returns the directory that stores runtime files.
- * @return string the directory that stores runtime files.
- * Defaults to the "runtime" subdirectory under [[basePath]].
- */
- public function getRuntimePath()
- {
- if ($this->_runtimePath === null) {
- $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
- }
- return $this->_runtimePath;
- }
-
- /**
- * Sets the directory that stores runtime files.
- * @param string $path the directory that stores runtime files.
- */
- public function setRuntimePath($path)
- {
- $this->_runtimePath = Yii::getAlias($path);
- Yii::setAlias('@runtime', $this->_runtimePath);
- }
-
- private $_vendorPath;
-
- /**
- * Returns the directory that stores vendor files.
- * @return string the directory that stores vendor files.
- * Defaults to "vendor" directory under [[basePath]].
- */
- public function getVendorPath()
- {
- if ($this->_vendorPath === null) {
- $this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor');
- }
- return $this->_vendorPath;
- }
-
- /**
- * Sets the directory that stores vendor files.
- * @param string $path the directory that stores vendor files.
- */
- public function setVendorPath($path)
- {
- $this->_vendorPath = Yii::getAlias($path);
- Yii::setAlias('@vendor', $this->_vendorPath);
- }
-
- /**
- * Returns the time zone used by this application.
- * This is a simple wrapper of PHP function date_default_timezone_get().
- * If time zone is not configured in php.ini or application config,
- * it will be set to UTC by default.
- * @return string the time zone used by this application.
- * @see http://php.net/manual/en/function.date-default-timezone-get.php
- */
- public function getTimeZone()
- {
- return date_default_timezone_get();
- }
-
- /**
- * Sets the time zone used by this application.
- * This is a simple wrapper of PHP function date_default_timezone_set().
- * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
- * @param string $value the time zone used by this application.
- * @see http://php.net/manual/en/function.date-default-timezone-set.php
- */
- public function setTimeZone($value)
- {
- date_default_timezone_set($value);
- }
-
- /**
- * Returns the database connection component.
- * @return \yii\db\Connection the database connection
- */
- public function getDb()
- {
- return $this->getComponent('db');
- }
-
- /**
- * Returns the log component.
- * @return \yii\log\Logger the log component
- */
- public function getLog()
- {
- return $this->getComponent('log');
- }
-
- /**
- * Returns the error handler component.
- * @return ErrorHandler the error handler application component.
- */
- public function getErrorHandler()
- {
- return $this->getComponent('errorHandler');
- }
-
- /**
- * Returns the cache component.
- * @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
- */
- public function getCache()
- {
- return $this->getComponent('cache');
- }
-
- /**
- * Returns the formatter component.
- * @return \yii\base\Formatter the formatter application component.
- */
- public function getFormatter()
- {
- return $this->getComponent('formatter');
- }
-
- /**
- * Returns the request component.
- * @return \yii\web\Request|\yii\console\Request the request component
- */
- public function getRequest()
- {
- return $this->getComponent('request');
- }
-
- /**
- * Returns the view object.
- * @return View|\yii\web\View the view object that is used to render various view files.
- */
- public function getView()
- {
- return $this->getComponent('view');
- }
-
- /**
- * Returns the URL manager for this application.
- * @return \yii\web\UrlManager the URL manager for this application.
- */
- public function getUrlManager()
- {
- return $this->getComponent('urlManager');
- }
-
- /**
- * Returns the internationalization (i18n) component
- * @return \yii\i18n\I18N the internationalization component
- */
- public function getI18n()
- {
- return $this->getComponent('i18n');
- }
-
- /**
- * Returns the mailer component.
- * @return \yii\mail\MailerInterface the mailer interface
- */
- public function getMail()
- {
- return $this->getComponent('mail');
- }
-
- /**
- * Returns the auth manager for this application.
- * @return \yii\rbac\Manager the auth manager for this application.
- */
- public function getAuthManager()
- {
- return $this->getComponent('authManager');
- }
-
- /**
- * Registers the core application components.
- * @see setComponents
- */
- public function registerCoreComponents()
- {
- $this->setComponents([
- 'log' => ['class' => 'yii\log\Logger'],
- 'errorHandler' => ['class' => 'yii\base\ErrorHandler'],
- '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'],
- ]);
- }
-
- /**
- * Handles uncaught PHP exceptions.
- *
- * This method is implemented as a PHP exception handler.
- *
- * @param \Exception $exception the exception that is not caught
- */
- public function handleException($exception)
- {
- $this->exception = $exception;
-
- // disable error capturing to avoid recursive errors while handling exceptions
- restore_error_handler();
- restore_exception_handler();
- try {
- $this->logException($exception);
- if (($handler = $this->getErrorHandler()) !== null) {
- $handler->handle($exception);
- } else {
- echo $this->renderException($exception);
- if (PHP_SAPI === 'cli' && !YII_ENV_TEST) {
- exit(1);
- }
- }
- } catch (\Exception $e) {
- // exception could be thrown in ErrorHandler::handle()
- $msg = (string)$e;
- $msg .= "\nPrevious exception:\n";
- $msg .= (string)$exception;
- if (YII_DEBUG) {
- if (PHP_SAPI === 'cli') {
- echo $msg . "\n";
- } else {
- echo '
';
- }
- }
-
- /**
- * Logs the given exception
- * @param \Exception $exception the exception to be logged
- */
- protected function logException($exception)
- {
- $category = get_class($exception);
- if ($exception instanceof HttpException) {
- $category = 'yii\\web\\HttpException:' . $exception->statusCode;
- } elseif ($exception instanceof \ErrorException) {
- $category .= ':' . $exception->getSeverity();
- }
- Yii::error((string)$exception, $category);
- }
+ /**
+ * @event Event an event raised before the application starts to handle a request.
+ */
+ const EVENT_BEFORE_REQUEST = 'beforeRequest';
+ /**
+ * @event Event an event raised after the application successfully handles a request (before the response is sent out).
+ */
+ const EVENT_AFTER_REQUEST = 'afterRequest';
+ /**
+ * @event ActionEvent an event raised before executing a controller action.
+ * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
+ */
+ const EVENT_BEFORE_ACTION = 'beforeAction';
+ /**
+ * @event ActionEvent an event raised after executing a controller action.
+ */
+ const EVENT_AFTER_ACTION = 'afterAction';
+
+ /**
+ * @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.
+ */
+ public $name = 'My Application';
+ /**
+ * @var string the version of this application.
+ */
+ public $version = '1.0';
+ /**
+ * @var string the charset currently used for the application.
+ */
+ public $charset = 'UTF-8';
+ /**
+ * @var string the language that is meant to be used for end users.
+ * @see sourceLanguage
+ */
+ public $language = 'en';
+ /**
+ * @var string the language that the application is written in. This mainly refers to
+ * the language that the messages and view files are written in.
+ * @see language
+ */
+ public $sourceLanguage = 'en';
+ /**
+ * @var Controller the currently active controller instance
+ */
+ public $controller;
+ /**
+ * @var string|boolean the layout that should be applied for views in this application. Defaults to 'main'.
+ * If this is false, layout will be disabled.
+ */
+ public $layout = 'main';
+ /**
+ * @var integer the size of the reserved memory. A portion of memory is pre-allocated so that
+ * when an out-of-memory issue occurs, the error handler is able to handle the error with
+ * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
+ * Defaults to 256KB.
+ */
+ public $memoryReserveSize = 262144;
+ /**
+ * @var string the requested route
+ */
+ public $requestedRoute;
+ /**
+ * @var Action the requested Action. If null, it means the request cannot be resolved into an action.
+ */
+ public $requestedAction;
+ /**
+ * @var array the parameters supplied to the requested action.
+ */
+ public $requestedParams;
+ /**
+ * @var array list of installed Yii extensions. Each array element represents a single extension
+ * with the following structure:
+ *
+ * ~~~
+ * [
+ * 'name' => 'extension name',
+ * 'version' => 'version number',
+ * 'bootstrap' => 'BootstrapClassName',
+ * ]
+ * ~~~
+ */
+ public $extensions = [];
+ /**
+ * @var \Exception the exception that is being handled currently. When this is not null,
+ * it means the application is handling some exception and extra care should be taken.
+ */
+ public $exception;
+
+ /**
+ * @var string Used to reserve memory for fatal error handler.
+ */
+ private $_memoryReserve;
+
+ /**
+ * Constructor.
+ * @param array $config name-value pairs that will be used to initialize the object properties.
+ * Note that the configuration must contain both [[id]] and [[basePath]].
+ * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
+ */
+ public function __construct($config = [])
+ {
+ Yii::$app = $this;
+
+ $this->preInit($config);
+ $this->registerErrorHandlers();
+ $this->registerCoreComponents();
+
+ Component::__construct($config);
+ }
+
+ /**
+ * Pre-initializes the application.
+ * This method is called at the beginning of the application constructor.
+ * It initializes several important application properties.
+ * If you override this method, please make sure you call the parent implementation.
+ * @param array $config the application configuration
+ * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
+ */
+ public function preInit(&$config)
+ {
+ if (!isset($config['id'])) {
+ throw new InvalidConfigException('The "id" configuration is required.');
+ }
+ if (isset($config['basePath'])) {
+ $this->setBasePath($config['basePath']);
+ unset($config['basePath']);
+ } else {
+ throw new InvalidConfigException('The "basePath" configuration is required.');
+ }
+
+ if (isset($config['vendorPath'])) {
+ $this->setVendorPath($config['vendorPath']);
+ unset($config['vendorPath']);
+ } else {
+ // set "@vendor"
+ $this->getVendorPath();
+ }
+ if (isset($config['runtimePath'])) {
+ $this->setRuntimePath($config['runtimePath']);
+ unset($config['runtimePath']);
+ } else {
+ // set "@runtime"
+ $this->getRuntimePath();
+ }
+
+ if (isset($config['timeZone'])) {
+ $this->setTimeZone($config['timeZone']);
+ unset($config['timeZone']);
+ } elseif (!ini_get('date.timezone')) {
+ $this->setTimeZone('UTC');
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ $this->initExtensions($this->extensions);
+ parent::init();
+ }
+
+ /**
+ * Initializes the extensions.
+ * @param array $extensions the extensions to be initialized. Please refer to [[extensions]]
+ * for the structure of the extension array.
+ */
+ protected function initExtensions($extensions)
+ {
+ foreach ($extensions as $extension) {
+ if (!empty($extension['alias'])) {
+ foreach ($extension['alias'] as $name => $path) {
+ Yii::setAlias($name, $path);
+ }
+ }
+ if (isset($extension['bootstrap'])) {
+ /** @var Extension $class */
+ $class = $extension['bootstrap'];
+ $class::init();
+ }
+ }
+ }
+
+ /**
+ * Loads components that are declared in [[preload]].
+ * @throws InvalidConfigException if a component or module to be preloaded is unknown
+ */
+ public function preloadComponents()
+ {
+ $this->getComponent('log');
+ parent::preloadComponents();
+ }
+
+ /**
+ * Registers error handlers.
+ */
+ public function registerErrorHandlers()
+ {
+ if (YII_ENABLE_ERROR_HANDLER) {
+ ini_set('display_errors', 0);
+ set_exception_handler([$this, 'handleException']);
+ set_error_handler([$this, 'handleError']);
+ if ($this->memoryReserveSize > 0) {
+ $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
+ }
+ register_shutdown_function([$this, 'handleFatalError']);
+ }
+ }
+
+ /**
+ * Returns an ID that uniquely identifies this module among all modules within the current application.
+ * Since this is an application instance, it will always return an empty string.
+ * @return string the unique ID of the module.
+ */
+ public function getUniqueId()
+ {
+ return '';
+ }
+
+ /**
+ * Sets the root directory of the application and the @app alias.
+ * This method can only be invoked at the beginning of the constructor.
+ * @param string $path the root directory of the application.
+ * @property string the root directory of the application.
+ * @throws InvalidParamException if the directory does not exist.
+ */
+ public function setBasePath($path)
+ {
+ parent::setBasePath($path);
+ Yii::setAlias('@app', $this->getBasePath());
+ }
+
+ /**
+ * Runs the application.
+ * This is the main entrance of an application.
+ * @return integer the exit status (0 means normal, non-zero values mean abnormal)
+ */
+ public function run()
+ {
+ $this->trigger(self::EVENT_BEFORE_REQUEST);
+ $response = $this->handleRequest($this->getRequest());
+ $this->trigger(self::EVENT_AFTER_REQUEST);
+ $response->send();
+
+ return $response->exitStatus;
+ }
+
+ /**
+ * Handles the specified request.
+ *
+ * This method should return an instance of [[Response]] or its child class
+ * which represents the handling result of the request.
+ *
+ * @param Request $request the request to be handled
+ * @return Response the resulting response
+ */
+ abstract public function handleRequest($request);
+
+ private $_runtimePath;
+
+ /**
+ * Returns the directory that stores runtime files.
+ * @return string the directory that stores runtime files.
+ * Defaults to the "runtime" subdirectory under [[basePath]].
+ */
+ public function getRuntimePath()
+ {
+ if ($this->_runtimePath === null) {
+ $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
+ }
+
+ return $this->_runtimePath;
+ }
+
+ /**
+ * Sets the directory that stores runtime files.
+ * @param string $path the directory that stores runtime files.
+ */
+ public function setRuntimePath($path)
+ {
+ $this->_runtimePath = Yii::getAlias($path);
+ Yii::setAlias('@runtime', $this->_runtimePath);
+ }
+
+ private $_vendorPath;
+
+ /**
+ * Returns the directory that stores vendor files.
+ * @return string the directory that stores vendor files.
+ * Defaults to "vendor" directory under [[basePath]].
+ */
+ public function getVendorPath()
+ {
+ if ($this->_vendorPath === null) {
+ $this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor');
+ }
+
+ return $this->_vendorPath;
+ }
+
+ /**
+ * Sets the directory that stores vendor files.
+ * @param string $path the directory that stores vendor files.
+ */
+ public function setVendorPath($path)
+ {
+ $this->_vendorPath = Yii::getAlias($path);
+ Yii::setAlias('@vendor', $this->_vendorPath);
+ }
+
+ /**
+ * Returns the time zone used by this application.
+ * This is a simple wrapper of PHP function date_default_timezone_get().
+ * If time zone is not configured in php.ini or application config,
+ * it will be set to UTC by default.
+ * @return string the time zone used by this application.
+ * @see http://php.net/manual/en/function.date-default-timezone-get.php
+ */
+ public function getTimeZone()
+ {
+ return date_default_timezone_get();
+ }
+
+ /**
+ * Sets the time zone used by this application.
+ * This is a simple wrapper of PHP function date_default_timezone_set().
+ * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
+ * @param string $value the time zone used by this application.
+ * @see http://php.net/manual/en/function.date-default-timezone-set.php
+ */
+ public function setTimeZone($value)
+ {
+ date_default_timezone_set($value);
+ }
+
+ /**
+ * Returns the database connection component.
+ * @return \yii\db\Connection the database connection
+ */
+ public function getDb()
+ {
+ return $this->getComponent('db');
+ }
+
+ /**
+ * Returns the log component.
+ * @return \yii\log\Logger the log component
+ */
+ public function getLog()
+ {
+ return $this->getComponent('log');
+ }
+
+ /**
+ * Returns the error handler component.
+ * @return ErrorHandler the error handler application component.
+ */
+ public function getErrorHandler()
+ {
+ return $this->getComponent('errorHandler');
+ }
+
+ /**
+ * Returns the cache component.
+ * @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
+ */
+ public function getCache()
+ {
+ return $this->getComponent('cache');
+ }
+
+ /**
+ * Returns the formatter component.
+ * @return \yii\base\Formatter the formatter application component.
+ */
+ public function getFormatter()
+ {
+ return $this->getComponent('formatter');
+ }
+
+ /**
+ * Returns the request component.
+ * @return \yii\web\Request|\yii\console\Request the request component
+ */
+ public function getRequest()
+ {
+ return $this->getComponent('request');
+ }
+
+ /**
+ * Returns the view object.
+ * @return View|\yii\web\View the view object that is used to render various view files.
+ */
+ public function getView()
+ {
+ return $this->getComponent('view');
+ }
+
+ /**
+ * Returns the URL manager for this application.
+ * @return \yii\web\UrlManager the URL manager for this application.
+ */
+ public function getUrlManager()
+ {
+ return $this->getComponent('urlManager');
+ }
+
+ /**
+ * Returns the internationalization (i18n) component
+ * @return \yii\i18n\I18N the internationalization component
+ */
+ public function getI18n()
+ {
+ return $this->getComponent('i18n');
+ }
+
+ /**
+ * Returns the mailer component.
+ * @return \yii\mail\MailerInterface the mailer interface
+ */
+ public function getMail()
+ {
+ return $this->getComponent('mail');
+ }
+
+ /**
+ * Returns the auth manager for this application.
+ * @return \yii\rbac\Manager the auth manager for this application.
+ */
+ public function getAuthManager()
+ {
+ return $this->getComponent('authManager');
+ }
+
+ /**
+ * Registers the core application components.
+ * @see setComponents
+ */
+ public function registerCoreComponents()
+ {
+ $this->setComponents([
+ 'log' => ['class' => 'yii\log\Logger'],
+ 'errorHandler' => ['class' => 'yii\base\ErrorHandler'],
+ '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'],
+ ]);
+ }
+
+ /**
+ * Handles uncaught PHP exceptions.
+ *
+ * This method is implemented as a PHP exception handler.
+ *
+ * @param \Exception $exception the exception that is not caught
+ */
+ public function handleException($exception)
+ {
+ $this->exception = $exception;
+
+ // disable error capturing to avoid recursive errors while handling exceptions
+ restore_error_handler();
+ restore_exception_handler();
+ try {
+ $this->logException($exception);
+ if (($handler = $this->getErrorHandler()) !== null) {
+ $handler->handle($exception);
+ } else {
+ echo $this->renderException($exception);
+ if (PHP_SAPI === 'cli' && !YII_ENV_TEST) {
+ exit(1);
+ }
+ }
+ } catch (\Exception $e) {
+ // exception could be thrown in ErrorHandler::handle()
+ $msg = (string) $e;
+ $msg .= "\nPrevious exception:\n";
+ $msg .= (string) $exception;
+ if (YII_DEBUG) {
+ if (PHP_SAPI === 'cli') {
+ echo $msg . "\n";
+ } else {
+ echo '
';
+ }
+ }
+
+ /**
+ * Logs the given exception
+ * @param \Exception $exception the exception to be logged
+ */
+ protected function logException($exception)
+ {
+ $category = get_class($exception);
+ if ($exception instanceof HttpException) {
+ $category = 'yii\\web\\HttpException:' . $exception->statusCode;
+ } elseif ($exception instanceof \ErrorException) {
+ $category .= ':' . $exception->getSeverity();
+ }
+ Yii::error((string) $exception, $category);
+ }
}
diff --git a/framework/base/ArrayAccessTrait.php b/framework/base/ArrayAccessTrait.php
index 3ccbd975d2f..43b7fff1736 100644
--- a/framework/base/ArrayAccessTrait.php
+++ b/framework/base/ArrayAccessTrait.php
@@ -18,63 +18,63 @@
*/
trait ArrayAccessTrait
{
- /**
- * Returns an iterator for traversing the data.
- * This method is required by the SPL interface `IteratorAggregate`.
- * It will be implicitly called when you use `foreach` to traverse the collection.
- * @return \ArrayIterator an iterator for traversing the cookies in the collection.
- */
- public function getIterator()
- {
- return new \ArrayIterator($this->data);
- }
+ /**
+ * Returns an iterator for traversing the data.
+ * This method is required by the SPL interface `IteratorAggregate`.
+ * It will be implicitly called when you use `foreach` to traverse the collection.
+ * @return \ArrayIterator an iterator for traversing the cookies in the collection.
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->data);
+ }
- /**
- * Returns the number of data items.
- * This method is required by Countable interface.
- * @return integer number of data elements.
- */
- public function count()
- {
- return count($this->data);
- }
+ /**
+ * Returns the number of data items.
+ * This method is required by Countable interface.
+ * @return integer number of data elements.
+ */
+ public function count()
+ {
+ return count($this->data);
+ }
- /**
- * This method is required by the interface ArrayAccess.
- * @param mixed $offset the offset to check on
- * @return boolean
- */
- public function offsetExists($offset)
- {
- return isset($this->data[$offset]);
- }
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to check on
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
- /**
- * This method is required by the interface ArrayAccess.
- * @param integer $offset the offset to retrieve element.
- * @return mixed the element at the offset, null if no element is found at the offset
- */
- public function offsetGet($offset)
- {
- return isset($this->data[$offset]) ? $this->data[$offset] : null;
- }
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param integer $offset the offset to retrieve element.
+ * @return mixed the element at the offset, null if no element is found at the offset
+ */
+ public function offsetGet($offset)
+ {
+ return isset($this->data[$offset]) ? $this->data[$offset] : null;
+ }
- /**
- * This method is required by the interface ArrayAccess.
- * @param integer $offset the offset to set element
- * @param mixed $item the element value
- */
- public function offsetSet($offset, $item)
- {
- $this->data[$offset] = $item;
- }
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param integer $offset the offset to set element
+ * @param mixed $item the element value
+ */
+ public function offsetSet($offset, $item)
+ {
+ $this->data[$offset] = $item;
+ }
- /**
- * This method is required by the interface ArrayAccess.
- * @param mixed $offset the offset to unset element
- */
- public function offsetUnset($offset)
- {
- unset($this->data[$offset]);
- }
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to unset element
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->data[$offset]);
+ }
}
diff --git a/framework/base/Arrayable.php b/framework/base/Arrayable.php
index 694424576fa..4a8fd45d8e4 100644
--- a/framework/base/Arrayable.php
+++ b/framework/base/Arrayable.php
@@ -22,69 +22,69 @@
*/
interface Arrayable
{
- /**
- * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
- *
- * A field is a named element in the returned array by [[toArray()]].
- *
- * This method should return an array of field names or field definitions.
- * If the former, the field name will be treated as an object property name whose value will be used
- * as the field value. If the latter, the array key should be the field name while the array value should be
- * the corresponding field definition which can be either an object property name or a PHP callable
- * returning the corresponding field value. The signature of the callable should be:
- *
- * ```php
- * function ($field, $model) {
- * // return field value
- * }
- * ```
- *
- * For example, the following code declares four fields:
- *
- * - `email`: the field name is the same as the property name `email`;
- * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
- * values are obtained from the `first_name` and `last_name` properties;
- * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
- * and `last_name`.
- *
- * ```php
- * return [
- * 'email',
- * 'firstName' => 'first_name',
- * 'lastName' => 'last_name',
- * 'fullName' => function () {
- * return $this->first_name . ' ' . $this->last_name;
- * },
- * ];
- * ```
- *
- * @return array the list of field names or field definitions.
- * @see toArray()
- */
- public function fields();
- /**
- * Returns the list of additional fields that can be returned by [[toArray()]] in addition to those listed in [[fields()]].
- *
- * This method is similar to [[fields()]] except that the list of fields declared
- * by this method are not returned by default by [[toArray()]]. Only when a field in the list
- * is explicitly requested, will it be included in the result of [[toArray()]].
- *
- * @return array the list of expandable field names or field definitions. Please refer
- * to [[fields()]] on the format of the return value.
- * @see toArray()
- * @see fields()
- */
- public function extraFields();
- /**
- * Converts the object into an array.
- *
- * @param array $fields the fields that the output array should contain. Fields not specified
- * in [[fields()]] will be ignored. If this parameter is empty, all fields as specified in [[fields()]] will be returned.
- * @param array $expand the additional fields that the output array should contain.
- * Fields not specified in [[extraFields()]] will be ignored. If this parameter is empty, no extra fields
- * will be returned.
- * @param boolean $recursive whether to recursively return array representation of embedded objects.
- * @return array the array representation of the object
- */
- public function toArray(array $fields = [], array $expand = [], $recursive = true);
+ /**
+ * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
+ *
+ * A field is a named element in the returned array by [[toArray()]].
+ *
+ * This method should return an array of field names or field definitions.
+ * If the former, the field name will be treated as an object property name whose value will be used
+ * as the field value. If the latter, the array key should be the field name while the array value should be
+ * the corresponding field definition which can be either an object property name or a PHP callable
+ * returning the corresponding field value. The signature of the callable should be:
+ *
+ * ```php
+ * function ($field, $model) {
+ * // return field value
+ * }
+ * ```
+ *
+ * For example, the following code declares four fields:
+ *
+ * - `email`: the field name is the same as the property name `email`;
+ * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
+ * values are obtained from the `first_name` and `last_name` properties;
+ * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
+ * and `last_name`.
+ *
+ * ```php
+ * return [
+ * 'email',
+ * 'firstName' => 'first_name',
+ * 'lastName' => 'last_name',
+ * 'fullName' => function () {
+ * return $this->first_name . ' ' . $this->last_name;
+ * },
+ * ];
+ * ```
+ *
+ * @return array the list of field names or field definitions.
+ * @see toArray()
+ */
+ public function fields();
+ /**
+ * Returns the list of additional fields that can be returned by [[toArray()]] in addition to those listed in [[fields()]].
+ *
+ * This method is similar to [[fields()]] except that the list of fields declared
+ * by this method are not returned by default by [[toArray()]]. Only when a field in the list
+ * is explicitly requested, will it be included in the result of [[toArray()]].
+ *
+ * @return array the list of expandable field names or field definitions. Please refer
+ * to [[fields()]] on the format of the return value.
+ * @see toArray()
+ * @see fields()
+ */
+ public function extraFields();
+ /**
+ * Converts the object into an array.
+ *
+ * @param array $fields the fields that the output array should contain. Fields not specified
+ * in [[fields()]] will be ignored. If this parameter is empty, all fields as specified in [[fields()]] will be returned.
+ * @param array $expand the additional fields that the output array should contain.
+ * Fields not specified in [[extraFields()]] will be ignored. If this parameter is empty, no extra fields
+ * will be returned.
+ * @param boolean $recursive whether to recursively return array representation of embedded objects.
+ * @return array the array representation of the object
+ */
+ public function toArray(array $fields = [], array $expand = [], $recursive = true);
}
diff --git a/framework/base/ArrayableTrait.php b/framework/base/ArrayableTrait.php
index 344521efbd5..b30722b9986 100644
--- a/framework/base/ArrayableTrait.php
+++ b/framework/base/ArrayableTrait.php
@@ -23,145 +23,146 @@
*/
trait ArrayableTrait
{
- /**
- * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
- *
- * A field is a named element in the returned array by [[toArray()]].
- *
- * This method should return an array of field names or field definitions.
- * If the former, the field name will be treated as an object property name whose value will be used
- * as the field value. If the latter, the array key should be the field name while the array value should be
- * the corresponding field definition which can be either an object property name or a PHP callable
- * returning the corresponding field value. The signature of the callable should be:
- *
- * ```php
- * function ($field, $model) {
- * // return field value
- * }
- * ```
- *
- * For example, the following code declares four fields:
- *
- * - `email`: the field name is the same as the property name `email`;
- * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
- * values are obtained from the `first_name` and `last_name` properties;
- * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
- * and `last_name`.
- *
- * ```php
- * return [
- * 'email',
- * 'firstName' => 'first_name',
- * 'lastName' => 'last_name',
- * 'fullName' => function () {
- * return $this->first_name . ' ' . $this->last_name;
- * },
- * ];
- * ```
- *
- * In this method, you may also want to return different lists of fields based on some context
- * information. For example, depending on the privilege of the current application user,
- * you may return different sets of visible fields or filter out some fields.
- *
- * The default implementation of this method returns the public object member variables.
- *
- * @return array the list of field names or field definitions.
- * @see toArray()
- */
- public function fields()
- {
- $fields = array_keys(Yii::getObjectVars($this));
- return array_combine($fields, $fields);
- }
+ /**
+ * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
+ *
+ * A field is a named element in the returned array by [[toArray()]].
+ *
+ * This method should return an array of field names or field definitions.
+ * If the former, the field name will be treated as an object property name whose value will be used
+ * as the field value. If the latter, the array key should be the field name while the array value should be
+ * the corresponding field definition which can be either an object property name or a PHP callable
+ * returning the corresponding field value. The signature of the callable should be:
+ *
+ * ```php
+ * function ($field, $model) {
+ * // return field value
+ * }
+ * ```
+ *
+ * For example, the following code declares four fields:
+ *
+ * - `email`: the field name is the same as the property name `email`;
+ * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
+ * values are obtained from the `first_name` and `last_name` properties;
+ * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
+ * and `last_name`.
+ *
+ * ```php
+ * return [
+ * 'email',
+ * 'firstName' => 'first_name',
+ * 'lastName' => 'last_name',
+ * 'fullName' => function () {
+ * return $this->first_name . ' ' . $this->last_name;
+ * },
+ * ];
+ * ```
+ *
+ * In this method, you may also want to return different lists of fields based on some context
+ * information. For example, depending on the privilege of the current application user,
+ * you may return different sets of visible fields or filter out some fields.
+ *
+ * The default implementation of this method returns the public object member variables.
+ *
+ * @return array the list of field names or field definitions.
+ * @see toArray()
+ */
+ public function fields()
+ {
+ $fields = array_keys(Yii::getObjectVars($this));
- /**
- * Returns the list of fields that can be expanded further and returned by [[toArray()]].
- *
- * This method is similar to [[fields()]] except that the list of fields returned
- * by this method are not returned by default by [[toArray()]]. Only when field names
- * to be expanded are explicitly specified when calling [[toArray()]], will their values
- * be exported.
- *
- * The default implementation returns an empty array.
- *
- * You may override this method to return a list of expandable fields based on some context information
- * (e.g. the current application user).
- *
- * @return array the list of expandable field names or field definitions. Please refer
- * to [[fields()]] on the format of the return value.
- * @see toArray()
- * @see fields()
- */
- public function extraFields()
- {
- return [];
- }
+ return array_combine($fields, $fields);
+ }
- /**
- * Converts the model into an array.
- *
- * This method will first identify which fields to be included in the resulting array by calling [[resolveFields()]].
- * It will then turn the model into an array with these fields. If `$recursive` is true,
- * any embedded objects will also be converted into arrays.
- *
- * If the model implements the [[Linkable]] interface, the resulting array will also have a `_link` element
- * which refers to a list of links as specified by the interface.
- *
- * @param array $fields the fields being requested. If empty, all fields as specified by [[fields()]] will be returned.
- * @param array $expand the additional fields being requested for exporting. Only fields declared in [[extraFields()]]
- * will be considered.
- * @param boolean $recursive whether to recursively return array representation of embedded objects.
- * @return array the array representation of the object
- */
- public function toArray(array $fields = [], array $expand = [], $recursive = true)
- {
- $data = [];
- foreach ($this->resolveFields($fields, $expand) as $field => $definition) {
- $data[$field] = is_string($definition) ? $this->$definition : call_user_func($definition, $field, $this);
- }
+ /**
+ * Returns the list of fields that can be expanded further and returned by [[toArray()]].
+ *
+ * This method is similar to [[fields()]] except that the list of fields returned
+ * by this method are not returned by default by [[toArray()]]. Only when field names
+ * to be expanded are explicitly specified when calling [[toArray()]], will their values
+ * be exported.
+ *
+ * The default implementation returns an empty array.
+ *
+ * You may override this method to return a list of expandable fields based on some context information
+ * (e.g. the current application user).
+ *
+ * @return array the list of expandable field names or field definitions. Please refer
+ * to [[fields()]] on the format of the return value.
+ * @see toArray()
+ * @see fields()
+ */
+ public function extraFields()
+ {
+ return [];
+ }
- if ($this instanceof Linkable) {
- $data['_links'] = Link::serialize($this->getLinks());
- }
+ /**
+ * Converts the model into an array.
+ *
+ * This method will first identify which fields to be included in the resulting array by calling [[resolveFields()]].
+ * It will then turn the model into an array with these fields. If `$recursive` is true,
+ * any embedded objects will also be converted into arrays.
+ *
+ * If the model implements the [[Linkable]] interface, the resulting array will also have a `_link` element
+ * which refers to a list of links as specified by the interface.
+ *
+ * @param array $fields the fields being requested. If empty, all fields as specified by [[fields()]] will be returned.
+ * @param array $expand the additional fields being requested for exporting. Only fields declared in [[extraFields()]]
+ * will be considered.
+ * @param boolean $recursive whether to recursively return array representation of embedded objects.
+ * @return array the array representation of the object
+ */
+ public function toArray(array $fields = [], array $expand = [], $recursive = true)
+ {
+ $data = [];
+ foreach ($this->resolveFields($fields, $expand) as $field => $definition) {
+ $data[$field] = is_string($definition) ? $this->$definition : call_user_func($definition, $field, $this);
+ }
- return $recursive ? ArrayHelper::toArray($data) : $data;
- }
+ if ($this instanceof Linkable) {
+ $data['_links'] = Link::serialize($this->getLinks());
+ }
- /**
- * Determines which fields can be returned by [[toArray()]].
- * This method will check the requested fields against those declared in [[fields()]] and [[extraFields()]]
- * to determine which fields can be returned.
- * @param array $fields the fields being requested for exporting
- * @param array $expand the additional fields being requested for exporting
- * @return array the list of fields to be exported. The array keys are the field names, and the array values
- * are the corresponding object property names or PHP callables returning the field values.
- */
- protected function resolveFields(array $fields, array $expand)
- {
- $result = [];
+ return $recursive ? ArrayHelper::toArray($data) : $data;
+ }
- foreach ($this->fields() as $field => $definition) {
- if (is_integer($field)) {
- $field = $definition;
- }
- if (empty($fields) || in_array($field, $fields, true)) {
- $result[$field] = $definition;
- }
- }
+ /**
+ * Determines which fields can be returned by [[toArray()]].
+ * This method will check the requested fields against those declared in [[fields()]] and [[extraFields()]]
+ * to determine which fields can be returned.
+ * @param array $fields the fields being requested for exporting
+ * @param array $expand the additional fields being requested for exporting
+ * @return array the list of fields to be exported. The array keys are the field names, and the array values
+ * are the corresponding object property names or PHP callables returning the field values.
+ */
+ protected function resolveFields(array $fields, array $expand)
+ {
+ $result = [];
- if (empty($expand)) {
- return $result;
- }
+ foreach ($this->fields() as $field => $definition) {
+ if (is_integer($field)) {
+ $field = $definition;
+ }
+ if (empty($fields) || in_array($field, $fields, true)) {
+ $result[$field] = $definition;
+ }
+ }
- foreach ($this->extraFields() as $field => $definition) {
- if (is_integer($field)) {
- $field = $definition;
- }
- if (in_array($field, $expand, true)) {
- $result[$field] = $definition;
- }
- }
+ if (empty($expand)) {
+ return $result;
+ }
- return $result;
- }
+ foreach ($this->extraFields() as $field => $definition) {
+ if (is_integer($field)) {
+ $field = $definition;
+ }
+ if (in_array($field, $expand, true)) {
+ $result[$field] = $definition;
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/framework/base/Behavior.php b/framework/base/Behavior.php
index 0b1786d7b4d..67911437bd0 100644
--- a/framework/base/Behavior.php
+++ b/framework/base/Behavior.php
@@ -20,72 +20,72 @@
*/
class Behavior extends \yii\base\Object
{
- /**
- * @var Component the owner of this behavior
- */
- public $owner;
+ /**
+ * @var Component the owner of this behavior
+ */
+ public $owner;
- /**
- * Declares event handlers for the [[owner]]'s events.
- *
- * Child classes may override this method to declare what PHP callbacks should
- * be attached to the events of the [[owner]] component.
- *
- * The callbacks will be attached to the [[owner]]'s events when the behavior is
- * attached to the owner; and they will be detached from the events when
- * the behavior is detached from the component.
- *
- * The callbacks can be any of the followings:
- *
- * - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']`
- * - object method: `[$object, 'handleClick']`
- * - static method: `['Page', 'handleClick']`
- * - anonymous function: `function($event) { ... }`
- *
- * The following is an example:
- *
- * ~~~
- * [
- * Model::EVENT_BEFORE_VALIDATE => 'myBeforeValidate',
- * Model::EVENT_AFTER_VALIDATE => 'myAfterValidate',
- * ]
- * ~~~
- *
- * @return array events (array keys) and the corresponding event handler methods (array values).
- */
- public function events()
- {
- return [];
- }
+ /**
+ * Declares event handlers for the [[owner]]'s events.
+ *
+ * Child classes may override this method to declare what PHP callbacks should
+ * be attached to the events of the [[owner]] component.
+ *
+ * The callbacks will be attached to the [[owner]]'s events when the behavior is
+ * attached to the owner; and they will be detached from the events when
+ * the behavior is detached from the component.
+ *
+ * The callbacks can be any of the followings:
+ *
+ * - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']`
+ * - object method: `[$object, 'handleClick']`
+ * - static method: `['Page', 'handleClick']`
+ * - anonymous function: `function ($event) { ... }`
+ *
+ * The following is an example:
+ *
+ * ~~~
+ * [
+ * Model::EVENT_BEFORE_VALIDATE => 'myBeforeValidate',
+ * Model::EVENT_AFTER_VALIDATE => 'myAfterValidate',
+ * ]
+ * ~~~
+ *
+ * @return array events (array keys) and the corresponding event handler methods (array values).
+ */
+ public function events()
+ {
+ return [];
+ }
- /**
- * Attaches the behavior object to the component.
- * The default implementation will set the [[owner]] property
- * and attach event handlers as declared in [[events]].
- * Make sure you call the parent implementation if you override this method.
- * @param Component $owner the component that this behavior is to be attached to.
- */
- public function attach($owner)
- {
- $this->owner = $owner;
- foreach ($this->events() as $event => $handler) {
- $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
- }
- }
+ /**
+ * Attaches the behavior object to the component.
+ * The default implementation will set the [[owner]] property
+ * and attach event handlers as declared in [[events]].
+ * Make sure you call the parent implementation if you override this method.
+ * @param Component $owner the component that this behavior is to be attached to.
+ */
+ public function attach($owner)
+ {
+ $this->owner = $owner;
+ foreach ($this->events() as $event => $handler) {
+ $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
+ }
+ }
- /**
- * Detaches the behavior object from the component.
- * The default implementation will unset the [[owner]] property
- * and detach event handlers declared in [[events]].
- * Make sure you call the parent implementation if you override this method.
- */
- public function detach()
- {
- if ($this->owner) {
- foreach ($this->events() as $event => $handler) {
- $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
- }
- $this->owner = null;
- }
- }
+ /**
+ * Detaches the behavior object from the component.
+ * The default implementation will unset the [[owner]] property
+ * and detach event handlers declared in [[events]].
+ * Make sure you call the parent implementation if you override this method.
+ */
+ public function detach()
+ {
+ if ($this->owner) {
+ foreach ($this->events() as $event => $handler) {
+ $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
+ }
+ $this->owner = null;
+ }
+ }
}
diff --git a/framework/base/Component.php b/framework/base/Component.php
index 596eecaaf81..ad629a41ddc 100644
--- a/framework/base/Component.php
+++ b/framework/base/Component.php
@@ -28,7 +28,7 @@
* To attach an event handler to an event, call [[on()]]:
*
* ~~~
- * $post->on('update', function($event) {
+ * $post->on('update', function ($event) {
* // send email notification
* });
* ~~~
@@ -36,7 +36,7 @@
* In the above, an anonymous function is attached to the "update" event of the post. You may attach
* the following types of event handlers:
*
- * - anonymous function: `function($event) { ... }`
+ * - anonymous function: `function ($event) { ... }`
* - object method: `[$object, 'handleAdd']`
* - static class method: `['Page', 'handleAdd']`
* - global function: `'handleAdd'`
@@ -54,7 +54,7 @@
*
* ~~~
* [
- * 'on add' => function($event) { ... }
+ * 'on add' => function ($event) { ... }
* ]
* ~~~
*
@@ -64,7 +64,7 @@
* and then access it when the handler is invoked. You may do so by
*
* ~~~
- * $post->on('update', function($event) {
+ * $post->on('update', function ($event) {
* // the data can be accessed via $event->data
* }, $data);
* ~~~
@@ -97,559 +97,577 @@
*/
class Component extends Object
{
- /**
- * @var array the attached event handlers (event name => handlers)
- */
- private $_events = [];
- /**
- * @var Behavior[] the attached behaviors (behavior name => behavior)
- */
- private $_behaviors;
-
- /**
- * Returns the value of a component property.
- * This method will check in the following order and act accordingly:
- *
- * - a property defined by a getter: return the getter result
- * - a property of a behavior: return the behavior property value
- *
- * Do not call this method directly as it is a PHP magic method that
- * will be implicitly called when executing `$value = $component->property;`.
- * @param string $name the property name
- * @return mixed the property value or the value of a behavior's property
- * @throws UnknownPropertyException if the property is not defined
- * @throws InvalidCallException if the property is write-only.
- * @see __set()
- */
- public function __get($name)
- {
- $getter = 'get' . $name;
- if (method_exists($this, $getter)) {
- // read property, e.g. getName()
- return $this->$getter();
- } else {
- // behavior property
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $behavior) {
- if ($behavior->canGetProperty($name)) {
- return $behavior->$name;
- }
- }
- }
- if (method_exists($this, 'set' . $name)) {
- throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
- } else {
- throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
- }
- }
-
- /**
- * Sets the value of a component property.
- * This method will check in the following order and act accordingly:
- *
- * - a property defined by a setter: set the property value
- * - an event in the format of "on xyz": attach the handler to the event "xyz"
- * - a behavior in the format of "as xyz": attach the behavior named as "xyz"
- * - a property of a behavior: set the behavior property value
- *
- * Do not call this method directly as it is a PHP magic method that
- * will be implicitly called when executing `$component->property = $value;`.
- * @param string $name the property name or the event name
- * @param mixed $value the property value
- * @throws UnknownPropertyException if the property is not defined
- * @throws InvalidCallException if the property is read-only.
- * @see __get()
- */
- public function __set($name, $value)
- {
- $setter = 'set' . $name;
- if (method_exists($this, $setter)) {
- // set property
- $this->$setter($value);
- return;
- } elseif (strncmp($name, 'on ', 3) === 0) {
- // on event: attach event handler
- $this->on(trim(substr($name, 3)), $value);
- return;
- } elseif (strncmp($name, 'as ', 3) === 0) {
- // as behavior: attach behavior
- $name = trim(substr($name, 3));
- $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
- return;
- } else {
- // behavior property
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $behavior) {
- if ($behavior->canSetProperty($name)) {
- $behavior->$name = $value;
- return;
- }
- }
- }
- if (method_exists($this, 'get' . $name)) {
- throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
- } else {
- throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
- }
- }
-
- /**
- * Checks if a property value is null.
- * This method will check in the following order and act accordingly:
- *
- * - a property defined by a setter: return whether the property value is null
- * - a property of a behavior: return whether the property value is null
- *
- * Do not call this method directly as it is a PHP magic method that
- * will be implicitly called when executing `isset($component->property)`.
- * @param string $name the property name or the event name
- * @return boolean whether the named property is null
- */
- public function __isset($name)
- {
- $getter = 'get' . $name;
- if (method_exists($this, $getter)) {
- return $this->$getter() !== null;
- } else {
- // behavior property
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $behavior) {
- if ($behavior->canGetProperty($name)) {
- return $behavior->$name !== null;
- }
- }
- }
- return false;
- }
-
- /**
- * Sets a component property to be null.
- * This method will check in the following order and act accordingly:
- *
- * - a property defined by a setter: set the property value to be null
- * - a property of a behavior: set the property value to be null
- *
- * Do not call this method directly as it is a PHP magic method that
- * will be implicitly called when executing `unset($component->property)`.
- * @param string $name the property name
- * @throws InvalidCallException if the property is read only.
- */
- public function __unset($name)
- {
- $setter = 'set' . $name;
- if (method_exists($this, $setter)) {
- $this->$setter(null);
- return;
- } else {
- // behavior property
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $behavior) {
- if ($behavior->canSetProperty($name)) {
- $behavior->$name = null;
- return;
- }
- }
- }
- throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name);
- }
-
- /**
- * Calls the named method which is not a class method.
- *
- * This method will check if any attached behavior has
- * the named method and will execute it if available.
- *
- * Do not call this method directly as it is a PHP magic method that
- * will be implicitly called when an unknown method is being invoked.
- * @param string $name the method name
- * @param array $params method parameters
- * @return mixed the method return value
- * @throws UnknownMethodException when calling unknown method
- */
- public function __call($name, $params)
- {
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $object) {
- if ($object->hasMethod($name)) {
- return call_user_func_array([$object, $name], $params);
- }
- }
-
- throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
- }
-
- /**
- * This method is called after the object is created by cloning an existing one.
- * It removes all behaviors because they are attached to the old object.
- */
- public function __clone()
- {
- $this->_events = [];
- $this->_behaviors = null;
- }
-
- /**
- * Returns a value indicating whether a property is defined for this component.
- * A property is defined if:
- *
- * - the class has a getter or setter method associated with the specified name
- * (in this case, property name is case-insensitive);
- * - the class has a member variable with the specified name (when `$checkVars` is true);
- * - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
- *
- * @param string $name the property name
- * @param boolean $checkVars whether to treat member variables as properties
- * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
- * @return boolean whether the property is defined
- * @see canGetProperty()
- * @see canSetProperty()
- */
- public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
- {
- return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
- }
-
- /**
- * Returns a value indicating whether a property can be read.
- * A property can be read if:
- *
- * - the class has a getter method associated with the specified name
- * (in this case, property name is case-insensitive);
- * - the class has a member variable with the specified name (when `$checkVars` is true);
- * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true).
- *
- * @param string $name the property name
- * @param boolean $checkVars whether to treat member variables as properties
- * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
- * @return boolean whether the property can be read
- * @see canSetProperty()
- */
- public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
- {
- if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) {
- return true;
- } elseif ($checkBehaviors) {
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $behavior) {
- if ($behavior->canGetProperty($name, $checkVars)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns a value indicating whether a property can be set.
- * A property can be written if:
- *
- * - the class has a setter method associated with the specified name
- * (in this case, property name is case-insensitive);
- * - the class has a member variable with the specified name (when `$checkVars` is true);
- * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true).
- *
- * @param string $name the property name
- * @param boolean $checkVars whether to treat member variables as properties
- * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
- * @return boolean whether the property can be written
- * @see canGetProperty()
- */
- public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
- {
- if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
- return true;
- } elseif ($checkBehaviors) {
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $behavior) {
- if ($behavior->canSetProperty($name, $checkVars)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns a value indicating whether a method is defined.
- * A method is defined if:
- *
- * - the class has a method with the specified name
- * - an attached behavior has a method with the given name (when `$checkBehaviors` is true).
- *
- * @param string $name the property name
- * @param boolean $checkBehaviors whether to treat behaviors' methods as methods of this component
- * @return boolean whether the property is defined
- */
- public function hasMethod($name, $checkBehaviors = true)
- {
- if (method_exists($this, $name)) {
- return true;
- } elseif ($checkBehaviors) {
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $behavior) {
- if ($behavior->hasMethod($name)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns a list of behaviors that this component should behave as.
- *
- * Child classes may override this method to specify the behaviors they want to behave as.
- *
- * The return value of this method should be an array of behavior objects or configurations
- * indexed by behavior names. A behavior configuration can be either a string specifying
- * the behavior class or an array of the following structure:
- *
- * ~~~
- * 'behaviorName' => [
- * 'class' => 'BehaviorClass',
- * 'property1' => 'value1',
- * 'property2' => 'value2',
- * ]
- * ~~~
- *
- * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings
- * or integers. If the former, they uniquely identify the behaviors. If the latter, the corresponding
- * behaviors are anonymous and their properties and methods will NOT be made available via the component
- * (however, the behaviors can still respond to the component's events).
- *
- * Behaviors declared in this method will be attached to the component automatically (on demand).
- *
- * @return array the behavior configurations.
- */
- public function behaviors()
- {
- return [];
- }
-
- /**
- * Returns a value indicating whether there is any handler attached to the named event.
- * @param string $name the event name
- * @return boolean whether there is any handler attached to the event.
- */
- public function hasEventHandlers($name)
- {
- $this->ensureBehaviors();
- return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
- }
-
- /**
- * Attaches an event handler to an event.
- *
- * The event handler must be a valid PHP callback. The followings are
- * some examples:
- *
- * ~~~
- * function ($event) { ... } // anonymous function
- * [$object, 'handleClick'] // $object->handleClick()
- * ['Page', 'handleClick'] // Page::handleClick()
- * 'handleClick' // global function handleClick()
- * ~~~
- *
- * The event handler must be defined with the following signature,
- *
- * ~~~
- * function ($event)
- * ~~~
- *
- * where `$event` is an [[Event]] object which includes parameters associated with the event.
- *
- * @param string $name the event name
- * @param callable $handler the event handler
- * @param mixed $data the data to be passed to the event handler when the event is triggered.
- * When the event handler is invoked, this data can be accessed via [[Event::data]].
- * @see off()
- */
- public function on($name, $handler, $data = null)
- {
- $this->ensureBehaviors();
- $this->_events[$name][] = [$handler, $data];
- }
-
- /**
- * Detaches an existing event handler from this component.
- * This method is the opposite of [[on()]].
- * @param string $name event name
- * @param callable $handler the event handler to be removed.
- * If it is null, all handlers attached to the named event will be removed.
- * @return boolean if a handler is found and detached
- * @see on()
- */
- public function off($name, $handler = null)
- {
- $this->ensureBehaviors();
- if (empty($this->_events[$name])) {
- return false;
- }
- if ($handler === null) {
- unset($this->_events[$name]);
- return true;
- } else {
- $removed = false;
- foreach ($this->_events[$name] as $i => $event) {
- if ($event[0] === $handler) {
- unset($this->_events[$name][$i]);
- $removed = true;
- }
- }
- if ($removed) {
- $this->_events[$name] = array_values($this->_events[$name]);
- }
- return $removed;
- }
- }
-
- /**
- * Triggers an event.
- * This method represents the happening of an event. It invokes
- * all attached handlers for the event including class-level handlers.
- * @param string $name the event name
- * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
- */
- public function trigger($name, Event $event = null)
- {
- $this->ensureBehaviors();
- if (!empty($this->_events[$name])) {
- if ($event === null) {
- $event = new Event;
- }
- if ($event->sender === null) {
- $event->sender = $this;
- }
- $event->handled = false;
- $event->name = $name;
- foreach ($this->_events[$name] as $handler) {
- $event->data = $handler[1];
- call_user_func($handler[0], $event);
- // stop further handling if the event is handled
- if ($event->handled) {
- return;
- }
- }
- }
- // invoke class-level attached handlers
- Event::trigger($this, $name, $event);
- }
-
- /**
- * Returns the named behavior object.
- * @param string $name the behavior name
- * @return Behavior the behavior object, or null if the behavior does not exist
- */
- public function getBehavior($name)
- {
- $this->ensureBehaviors();
- return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
- }
-
- /**
- * Returns all behaviors attached to this component.
- * @return Behavior[] list of behaviors attached to this component
- */
- public function getBehaviors()
- {
- $this->ensureBehaviors();
- return $this->_behaviors;
- }
-
- /**
- * Attaches a behavior to this component.
- * This method will create the behavior object based on the given
- * configuration. After that, the behavior object will be attached to
- * this component by calling the [[Behavior::attach()]] method.
- * @param string $name the name of the behavior.
- * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
- *
- * - a [[Behavior]] object
- * - a string specifying the behavior class
- * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
- *
- * @return Behavior the behavior object
- * @see detachBehavior()
- */
- public function attachBehavior($name, $behavior)
- {
- $this->ensureBehaviors();
- return $this->attachBehaviorInternal($name, $behavior);
- }
-
- /**
- * Attaches a list of behaviors to the component.
- * Each behavior is indexed by its name and should be a [[Behavior]] object,
- * a string specifying the behavior class, or an configuration array for creating the behavior.
- * @param array $behaviors list of behaviors to be attached to the component
- * @see attachBehavior()
- */
- public function attachBehaviors($behaviors)
- {
- $this->ensureBehaviors();
- foreach ($behaviors as $name => $behavior) {
- $this->attachBehaviorInternal($name, $behavior);
- }
- }
-
- /**
- * Detaches a behavior from the component.
- * The behavior's [[Behavior::detach()]] method will be invoked.
- * @param string $name the behavior's name.
- * @return Behavior the detached behavior. Null if the behavior does not exist.
- */
- public function detachBehavior($name)
- {
- $this->ensureBehaviors();
- if (isset($this->_behaviors[$name])) {
- $behavior = $this->_behaviors[$name];
- unset($this->_behaviors[$name]);
- $behavior->detach();
- return $behavior;
- } else {
- return null;
- }
- }
-
- /**
- * Detaches all behaviors from the component.
- */
- public function detachBehaviors()
- {
- $this->ensureBehaviors();
- foreach ($this->_behaviors as $name => $behavior) {
- $this->detachBehavior($name);
- }
- }
-
- /**
- * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
- */
- public function ensureBehaviors()
- {
- if ($this->_behaviors === null) {
- $this->_behaviors = [];
- foreach ($this->behaviors() as $name => $behavior) {
- $this->attachBehaviorInternal($name, $behavior);
- }
- }
- }
-
- /**
- * Attaches a behavior to this component.
- * @param string $name the name of the behavior.
- * @param string|array|Behavior $behavior the behavior to be attached
- * @return Behavior the attached behavior.
- */
- private function attachBehaviorInternal($name, $behavior)
- {
- if (!($behavior instanceof Behavior)) {
- $behavior = Yii::createObject($behavior);
- }
- if (isset($this->_behaviors[$name])) {
- $this->_behaviors[$name]->detach();
- }
- $behavior->attach($this);
- return $this->_behaviors[$name] = $behavior;
- }
+ /**
+ * @var array the attached event handlers (event name => handlers)
+ */
+ private $_events = [];
+ /**
+ * @var Behavior[] the attached behaviors (behavior name => behavior)
+ */
+ private $_behaviors;
+
+ /**
+ * Returns the value of a component property.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a getter: return the getter result
+ * - a property of a behavior: return the behavior property value
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `$value = $component->property;`.
+ * @param string $name the property name
+ * @return mixed the property value or the value of a behavior's property
+ * @throws UnknownPropertyException if the property is not defined
+ * @throws InvalidCallException if the property is write-only.
+ * @see __set()
+ */
+ public function __get($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ // read property, e.g. getName()
+ return $this->$getter();
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canGetProperty($name)) {
+ return $behavior->$name;
+ }
+ }
+ }
+ if (method_exists($this, 'set' . $name)) {
+ throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
+
+ /**
+ * Sets the value of a component property.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a setter: set the property value
+ * - an event in the format of "on xyz": attach the handler to the event "xyz"
+ * - a behavior in the format of "as xyz": attach the behavior named as "xyz"
+ * - a property of a behavior: set the behavior property value
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `$component->property = $value;`.
+ * @param string $name the property name or the event name
+ * @param mixed $value the property value
+ * @throws UnknownPropertyException if the property is not defined
+ * @throws InvalidCallException if the property is read-only.
+ * @see __get()
+ */
+ public function __set($name, $value)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ // set property
+ $this->$setter($value);
+
+ return;
+ } elseif (strncmp($name, 'on ', 3) === 0) {
+ // on event: attach event handler
+ $this->on(trim(substr($name, 3)), $value);
+
+ return;
+ } elseif (strncmp($name, 'as ', 3) === 0) {
+ // as behavior: attach behavior
+ $name = trim(substr($name, 3));
+ $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
+
+ return;
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canSetProperty($name)) {
+ $behavior->$name = $value;
+
+ return;
+ }
+ }
+ }
+ if (method_exists($this, 'get' . $name)) {
+ throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
+
+ /**
+ * Checks if a property value is null.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a setter: return whether the property value is null
+ * - a property of a behavior: return whether the property value is null
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `isset($component->property)`.
+ * @param string $name the property name or the event name
+ * @return boolean whether the named property is null
+ */
+ public function __isset($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ return $this->$getter() !== null;
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canGetProperty($name)) {
+ return $behavior->$name !== null;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets a component property to be null.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a setter: set the property value to be null
+ * - a property of a behavior: set the property value to be null
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `unset($component->property)`.
+ * @param string $name the property name
+ * @throws InvalidCallException if the property is read only.
+ */
+ public function __unset($name)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ $this->$setter(null);
+
+ return;
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canSetProperty($name)) {
+ $behavior->$name = null;
+
+ return;
+ }
+ }
+ }
+ throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name);
+ }
+
+ /**
+ * Calls the named method which is not a class method.
+ *
+ * This method will check if any attached behavior has
+ * the named method and will execute it if available.
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when an unknown method is being invoked.
+ * @param string $name the method name
+ * @param array $params method parameters
+ * @return mixed the method return value
+ * @throws UnknownMethodException when calling unknown method
+ */
+ public function __call($name, $params)
+ {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $object) {
+ if ($object->hasMethod($name)) {
+ return call_user_func_array([$object, $name], $params);
+ }
+ }
+
+ throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
+ }
+
+ /**
+ * This method is called after the object is created by cloning an existing one.
+ * It removes all behaviors because they are attached to the old object.
+ */
+ public function __clone()
+ {
+ $this->_events = [];
+ $this->_behaviors = null;
+ }
+
+ /**
+ * Returns a value indicating whether a property is defined for this component.
+ * A property is defined if:
+ *
+ * - the class has a getter or setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ * - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
+ * @return boolean whether the property is defined
+ * @see canGetProperty()
+ * @see canSetProperty()
+ */
+ public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
+ {
+ return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
+ }
+
+ /**
+ * Returns a value indicating whether a property can be read.
+ * A property can be read if:
+ *
+ * - the class has a getter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
+ * @return boolean whether the property can be read
+ * @see canSetProperty()
+ */
+ public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
+ {
+ if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) {
+ return true;
+ } elseif ($checkBehaviors) {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canGetProperty($name, $checkVars)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a value indicating whether a property can be set.
+ * A property can be written if:
+ *
+ * - the class has a setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
+ * @return boolean whether the property can be written
+ * @see canGetProperty()
+ */
+ public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
+ {
+ if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
+ return true;
+ } elseif ($checkBehaviors) {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canSetProperty($name, $checkVars)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a value indicating whether a method is defined.
+ * A method is defined if:
+ *
+ * - the class has a method with the specified name
+ * - an attached behavior has a method with the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkBehaviors whether to treat behaviors' methods as methods of this component
+ * @return boolean whether the property is defined
+ */
+ public function hasMethod($name, $checkBehaviors = true)
+ {
+ if (method_exists($this, $name)) {
+ return true;
+ } elseif ($checkBehaviors) {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->hasMethod($name)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a list of behaviors that this component should behave as.
+ *
+ * Child classes may override this method to specify the behaviors they want to behave as.
+ *
+ * The return value of this method should be an array of behavior objects or configurations
+ * indexed by behavior names. A behavior configuration can be either a string specifying
+ * the behavior class or an array of the following structure:
+ *
+ * ~~~
+ * 'behaviorName' => [
+ * 'class' => 'BehaviorClass',
+ * 'property1' => 'value1',
+ * 'property2' => 'value2',
+ * ]
+ * ~~~
+ *
+ * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings
+ * or integers. If the former, they uniquely identify the behaviors. If the latter, the corresponding
+ * behaviors are anonymous and their properties and methods will NOT be made available via the component
+ * (however, the behaviors can still respond to the component's events).
+ *
+ * Behaviors declared in this method will be attached to the component automatically (on demand).
+ *
+ * @return array the behavior configurations.
+ */
+ public function behaviors()
+ {
+ return [];
+ }
+
+ /**
+ * Returns a value indicating whether there is any handler attached to the named event.
+ * @param string $name the event name
+ * @return boolean whether there is any handler attached to the event.
+ */
+ public function hasEventHandlers($name)
+ {
+ $this->ensureBehaviors();
+
+ return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
+ }
+
+ /**
+ * Attaches an event handler to an event.
+ *
+ * The event handler must be a valid PHP callback. The followings are
+ * some examples:
+ *
+ * ~~~
+ * function ($event) { ... } // anonymous function
+ * [$object, 'handleClick'] // $object->handleClick()
+ * ['Page', 'handleClick'] // Page::handleClick()
+ * 'handleClick' // global function handleClick()
+ * ~~~
+ *
+ * The event handler must be defined with the following signature,
+ *
+ * ~~~
+ * function ($event)
+ * ~~~
+ *
+ * where `$event` is an [[Event]] object which includes parameters associated with the event.
+ *
+ * @param string $name the event name
+ * @param callable $handler the event handler
+ * @param mixed $data the data to be passed to the event handler when the event is triggered.
+ * When the event handler is invoked, this data can be accessed via [[Event::data]].
+ * @see off()
+ */
+ public function on($name, $handler, $data = null)
+ {
+ $this->ensureBehaviors();
+ $this->_events[$name][] = [$handler, $data];
+ }
+
+ /**
+ * Detaches an existing event handler from this component.
+ * This method is the opposite of [[on()]].
+ * @param string $name event name
+ * @param callable $handler the event handler to be removed.
+ * If it is null, all handlers attached to the named event will be removed.
+ * @return boolean if a handler is found and detached
+ * @see on()
+ */
+ public function off($name, $handler = null)
+ {
+ $this->ensureBehaviors();
+ if (empty($this->_events[$name])) {
+ return false;
+ }
+ if ($handler === null) {
+ unset($this->_events[$name]);
+
+ return true;
+ } else {
+ $removed = false;
+ foreach ($this->_events[$name] as $i => $event) {
+ if ($event[0] === $handler) {
+ unset($this->_events[$name][$i]);
+ $removed = true;
+ }
+ }
+ if ($removed) {
+ $this->_events[$name] = array_values($this->_events[$name]);
+ }
+
+ return $removed;
+ }
+ }
+
+ /**
+ * Triggers an event.
+ * This method represents the happening of an event. It invokes
+ * all attached handlers for the event including class-level handlers.
+ * @param string $name the event name
+ * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
+ */
+ public function trigger($name, Event $event = null)
+ {
+ $this->ensureBehaviors();
+ if (!empty($this->_events[$name])) {
+ if ($event === null) {
+ $event = new Event;
+ }
+ if ($event->sender === null) {
+ $event->sender = $this;
+ }
+ $event->handled = false;
+ $event->name = $name;
+ foreach ($this->_events[$name] as $handler) {
+ $event->data = $handler[1];
+ call_user_func($handler[0], $event);
+ // stop further handling if the event is handled
+ if ($event->handled) {
+ return;
+ }
+ }
+ }
+ // invoke class-level attached handlers
+ Event::trigger($this, $name, $event);
+ }
+
+ /**
+ * Returns the named behavior object.
+ * @param string $name the behavior name
+ * @return Behavior the behavior object, or null if the behavior does not exist
+ */
+ public function getBehavior($name)
+ {
+ $this->ensureBehaviors();
+
+ return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
+ }
+
+ /**
+ * Returns all behaviors attached to this component.
+ * @return Behavior[] list of behaviors attached to this component
+ */
+ public function getBehaviors()
+ {
+ $this->ensureBehaviors();
+
+ return $this->_behaviors;
+ }
+
+ /**
+ * Attaches a behavior to this component.
+ * This method will create the behavior object based on the given
+ * configuration. After that, the behavior object will be attached to
+ * this component by calling the [[Behavior::attach()]] method.
+ * @param string $name the name of the behavior.
+ * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
+ *
+ * - a [[Behavior]] object
+ * - a string specifying the behavior class
+ * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
+ *
+ * @return Behavior the behavior object
+ * @see detachBehavior()
+ */
+ public function attachBehavior($name, $behavior)
+ {
+ $this->ensureBehaviors();
+
+ return $this->attachBehaviorInternal($name, $behavior);
+ }
+
+ /**
+ * Attaches a list of behaviors to the component.
+ * Each behavior is indexed by its name and should be a [[Behavior]] object,
+ * a string specifying the behavior class, or an configuration array for creating the behavior.
+ * @param array $behaviors list of behaviors to be attached to the component
+ * @see attachBehavior()
+ */
+ public function attachBehaviors($behaviors)
+ {
+ $this->ensureBehaviors();
+ foreach ($behaviors as $name => $behavior) {
+ $this->attachBehaviorInternal($name, $behavior);
+ }
+ }
+
+ /**
+ * Detaches a behavior from the component.
+ * The behavior's [[Behavior::detach()]] method will be invoked.
+ * @param string $name the behavior's name.
+ * @return Behavior the detached behavior. Null if the behavior does not exist.
+ */
+ public function detachBehavior($name)
+ {
+ $this->ensureBehaviors();
+ if (isset($this->_behaviors[$name])) {
+ $behavior = $this->_behaviors[$name];
+ unset($this->_behaviors[$name]);
+ $behavior->detach();
+
+ return $behavior;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Detaches all behaviors from the component.
+ */
+ public function detachBehaviors()
+ {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $name => $behavior) {
+ $this->detachBehavior($name);
+ }
+ }
+
+ /**
+ * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
+ */
+ public function ensureBehaviors()
+ {
+ if ($this->_behaviors === null) {
+ $this->_behaviors = [];
+ foreach ($this->behaviors() as $name => $behavior) {
+ $this->attachBehaviorInternal($name, $behavior);
+ }
+ }
+ }
+
+ /**
+ * Attaches a behavior to this component.
+ * @param string $name the name of the behavior.
+ * @param string|array|Behavior $behavior the behavior to be attached
+ * @return Behavior the attached behavior.
+ */
+ private function attachBehaviorInternal($name, $behavior)
+ {
+ if (!($behavior instanceof Behavior)) {
+ $behavior = Yii::createObject($behavior);
+ }
+ if (isset($this->_behaviors[$name])) {
+ $this->_behaviors[$name]->detach();
+ }
+ $behavior->attach($this);
+
+ return $this->_behaviors[$name] = $behavior;
+ }
}
diff --git a/framework/base/Controller.php b/framework/base/Controller.php
index 27d234512bc..1a1d6bb9220 100644
--- a/framework/base/Controller.php
+++ b/framework/base/Controller.php
@@ -25,395 +25,400 @@
*/
class Controller extends Component implements ViewContextInterface
{
- /**
- * @event ActionEvent an event raised right before executing a controller action.
- * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
- */
- const EVENT_BEFORE_ACTION = 'beforeAction';
- /**
- * @event ActionEvent an event raised right after executing a controller action.
- */
- const EVENT_AFTER_ACTION = 'afterAction';
- /**
- * @var string the ID of this controller.
- */
- public $id;
- /**
- * @var Module $module the module that this controller belongs to.
- */
- public $module;
- /**
- * @var string the ID of the action that is used when the action ID is not specified
- * in the request. Defaults to 'index'.
- */
- public $defaultAction = 'index';
- /**
- * @var string|boolean the name of the layout to be applied to this controller's views.
- * This property mainly affects the behavior of [[render()]].
- * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value.
- * If false, no layout will be applied.
- */
- public $layout;
- /**
- * @var Action the action that is currently being executed. This property will be set
- * by [[run()]] when it is called by [[Application]] to run an action.
- */
- public $action;
- /**
- * @var View the view object that can be used to render views or view files.
- */
- private $_view;
+ /**
+ * @event ActionEvent an event raised right before executing a controller action.
+ * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
+ */
+ const EVENT_BEFORE_ACTION = 'beforeAction';
+ /**
+ * @event ActionEvent an event raised right after executing a controller action.
+ */
+ const EVENT_AFTER_ACTION = 'afterAction';
+ /**
+ * @var string the ID of this controller.
+ */
+ public $id;
+ /**
+ * @var Module $module the module that this controller belongs to.
+ */
+ public $module;
+ /**
+ * @var string the ID of the action that is used when the action ID is not specified
+ * in the request. Defaults to 'index'.
+ */
+ public $defaultAction = 'index';
+ /**
+ * @var string|boolean the name of the layout to be applied to this controller's views.
+ * This property mainly affects the behavior of [[render()]].
+ * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value.
+ * If false, no layout will be applied.
+ */
+ public $layout;
+ /**
+ * @var Action the action that is currently being executed. This property will be set
+ * by [[run()]] when it is called by [[Application]] to run an action.
+ */
+ public $action;
+ /**
+ * @var View the view object that can be used to render views or view files.
+ */
+ private $_view;
+ /**
+ * @param string $id the ID of this controller.
+ * @param Module $module the module that this controller belongs to.
+ * @param array $config name-value pairs that will be used to initialize the object properties.
+ */
+ public function __construct($id, $module, $config = [])
+ {
+ $this->id = $id;
+ $this->module = $module;
+ parent::__construct($config);
+ }
- /**
- * @param string $id the ID of this controller.
- * @param Module $module the module that this controller belongs to.
- * @param array $config name-value pairs that will be used to initialize the object properties.
- */
- public function __construct($id, $module, $config = [])
- {
- $this->id = $id;
- $this->module = $module;
- parent::__construct($config);
- }
+ /**
+ * Declares external actions for the controller.
+ * This method is meant to be overwritten to declare external actions for the controller.
+ * It should return an array, with array keys being action IDs, and array values the corresponding
+ * action class names or action configuration arrays. For example,
+ *
+ * ~~~
+ * return [
+ * 'action1' => 'app\components\Action1',
+ * 'action2' => [
+ * 'class' => 'app\components\Action2',
+ * 'property1' => 'value1',
+ * 'property2' => 'value2',
+ * ],
+ * ];
+ * ~~~
+ *
+ * [[\Yii::createObject()]] will be used later to create the requested action
+ * using the configuration provided here.
+ */
+ public function actions()
+ {
+ return [];
+ }
- /**
- * Declares external actions for the controller.
- * This method is meant to be overwritten to declare external actions for the controller.
- * It should return an array, with array keys being action IDs, and array values the corresponding
- * action class names or action configuration arrays. For example,
- *
- * ~~~
- * return [
- * 'action1' => 'app\components\Action1',
- * 'action2' => [
- * 'class' => 'app\components\Action2',
- * 'property1' => 'value1',
- * 'property2' => 'value2',
- * ],
- * ];
- * ~~~
- *
- * [[\Yii::createObject()]] will be used later to create the requested action
- * using the configuration provided here.
- */
- public function actions()
- {
- return [];
- }
+ /**
+ * Runs an action within this controller with the specified action ID and parameters.
+ * If the action ID is empty, the method will use [[defaultAction]].
+ * @param string $id the ID of the action to be executed.
+ * @param array $params the parameters (name-value pairs) to be passed to the action.
+ * @return mixed the result of the action.
+ * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
+ * @see createAction()
+ */
+ public function runAction($id, $params = [])
+ {
+ $action = $this->createAction($id);
+ if ($action !== null) {
+ Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__);
+ if (Yii::$app->requestedAction === null) {
+ Yii::$app->requestedAction = $action;
+ }
+ $oldAction = $this->action;
+ $this->action = $action;
+ $result = null;
+ $event = new ActionEvent($action);
+ Yii::$app->trigger(Application::EVENT_BEFORE_ACTION, $event);
+ if ($event->isValid && $this->module->beforeAction($action) && $this->beforeAction($action)) {
+ $result = $action->runWithParams($params);
+ $result = $this->afterAction($action, $result);
+ $result = $this->module->afterAction($action, $result);
+ $event = new ActionEvent($action);
+ $event->result = $result;
+ Yii::$app->trigger(Application::EVENT_AFTER_ACTION, $event);
+ $result = $event->result;
+ }
+ $this->action = $oldAction;
- /**
- * Runs an action within this controller with the specified action ID and parameters.
- * If the action ID is empty, the method will use [[defaultAction]].
- * @param string $id the ID of the action to be executed.
- * @param array $params the parameters (name-value pairs) to be passed to the action.
- * @return mixed the result of the action.
- * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
- * @see createAction()
- */
- public function runAction($id, $params = [])
- {
- $action = $this->createAction($id);
- if ($action !== null) {
- Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__);
- if (Yii::$app->requestedAction === null) {
- Yii::$app->requestedAction = $action;
- }
- $oldAction = $this->action;
- $this->action = $action;
- $result = null;
- $event = new ActionEvent($action);
- Yii::$app->trigger(Application::EVENT_BEFORE_ACTION, $event);
- if ($event->isValid && $this->module->beforeAction($action) && $this->beforeAction($action)) {
- $result = $action->runWithParams($params);
- $result = $this->afterAction($action, $result);
- $result = $this->module->afterAction($action, $result);
- $event = new ActionEvent($action);
- $event->result = $result;
- Yii::$app->trigger(Application::EVENT_AFTER_ACTION, $event);
- $result = $event->result;
- }
- $this->action = $oldAction;
- return $result;
- } else {
- throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
- }
- }
+ return $result;
+ } else {
+ throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
+ }
+ }
- /**
- * Runs a request specified in terms of a route.
- * The route can be either an ID of an action within this controller or a complete route consisting
- * of module IDs, controller ID and action ID. If the route starts with a slash '/', the parsing of
- * the route will start from the application; otherwise, it will start from the parent module of this controller.
- * @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
- * @param array $params the parameters to be passed to the action.
- * @return mixed the result of the action.
- * @see runAction()
- */
- public function run($route, $params = [])
- {
- $pos = strpos($route, '/');
- if ($pos === false) {
- return $this->runAction($route, $params);
- } elseif ($pos > 0) {
- return $this->module->runAction($route, $params);
- } else {
- return Yii::$app->runAction(ltrim($route, '/'), $params);
- }
- }
+ /**
+ * Runs a request specified in terms of a route.
+ * The route can be either an ID of an action within this controller or a complete route consisting
+ * of module IDs, controller ID and action ID. If the route starts with a slash '/', the parsing of
+ * the route will start from the application; otherwise, it will start from the parent module of this controller.
+ * @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
+ * @param array $params the parameters to be passed to the action.
+ * @return mixed the result of the action.
+ * @see runAction()
+ */
+ public function run($route, $params = [])
+ {
+ $pos = strpos($route, '/');
+ if ($pos === false) {
+ return $this->runAction($route, $params);
+ } elseif ($pos > 0) {
+ return $this->module->runAction($route, $params);
+ } else {
+ return Yii::$app->runAction(ltrim($route, '/'), $params);
+ }
+ }
- /**
- * Binds the parameters to the action.
- * This method is invoked by [[Action]] when it begins to run with the given parameters.
- * @param Action $action the action to be bound with parameters.
- * @param array $params the parameters to be bound to the action.
- * @return array the valid parameters that the action can run with.
- */
- public function bindActionParams($action, $params)
- {
- return [];
- }
+ /**
+ * Binds the parameters to the action.
+ * This method is invoked by [[Action]] when it begins to run with the given parameters.
+ * @param Action $action the action to be bound with parameters.
+ * @param array $params the parameters to be bound to the action.
+ * @return array the valid parameters that the action can run with.
+ */
+ public function bindActionParams($action, $params)
+ {
+ return [];
+ }
- /**
- * Creates an action based on the given action ID.
- * The method first checks if the action ID has been declared in [[actions()]]. If so,
- * it will use the configuration declared there to create the action object.
- * If not, it will look for a controller method whose name is in the format of `actionXyz`
- * where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that
- * method will be created and returned.
- * @param string $id the action ID.
- * @return Action the newly created action instance. Null if the ID doesn't resolve into any action.
- */
- public function createAction($id)
- {
- if ($id === '') {
- $id = $this->defaultAction;
- }
+ /**
+ * Creates an action based on the given action ID.
+ * The method first checks if the action ID has been declared in [[actions()]]. If so,
+ * it will use the configuration declared there to create the action object.
+ * If not, it will look for a controller method whose name is in the format of `actionXyz`
+ * where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that
+ * method will be created and returned.
+ * @param string $id the action ID.
+ * @return Action the newly created action instance. Null if the ID doesn't resolve into any action.
+ */
+ public function createAction($id)
+ {
+ if ($id === '') {
+ $id = $this->defaultAction;
+ }
- $actionMap = $this->actions();
- if (isset($actionMap[$id])) {
- return Yii::createObject($actionMap[$id], $id, $this);
- } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
- $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
- if (method_exists($this, $methodName)) {
- $method = new \ReflectionMethod($this, $methodName);
- if ($method->getName() === $methodName) {
- return new InlineAction($id, $this, $methodName);
- }
- }
- }
- return null;
- }
+ $actionMap = $this->actions();
+ if (isset($actionMap[$id])) {
+ return Yii::createObject($actionMap[$id], $id, $this);
+ } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
+ $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
+ if (method_exists($this, $methodName)) {
+ $method = new \ReflectionMethod($this, $methodName);
+ if ($method->getName() === $methodName) {
+ return new InlineAction($id, $this, $methodName);
+ }
+ }
+ }
- /**
- * This method is invoked right before an action is to be executed (after all possible filters).
- * You may override this method to do last-minute preparation for the action.
- * If you override this method, please make sure you call the parent implementation first.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- $event = new ActionEvent($action);
- $this->trigger(self::EVENT_BEFORE_ACTION, $event);
- return $event->isValid;
- }
+ return null;
+ }
- /**
- * This method is invoked right after an action is executed.
- * You may override this method to do some postprocessing for the action.
- * If you override this method, please make sure you call the parent implementation first.
- * Also make sure you return the action result, whether it is processed or not.
- * @param Action $action the action just executed.
- * @param mixed $result the action return result.
- * @return mixed the processed action result.
- */
- public function afterAction($action, $result)
- {
- $event = new ActionEvent($action);
- $event->result = $result;
- $this->trigger(self::EVENT_AFTER_ACTION, $event);
- return $event->result;
- }
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters).
+ * You may override this method to do last-minute preparation for the action.
+ * If you override this method, please make sure you call the parent implementation first.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $event = new ActionEvent($action);
+ $this->trigger(self::EVENT_BEFORE_ACTION, $event);
- /**
- * @return string the controller ID that is prefixed with the module ID (if any).
- */
- public function getUniqueId()
- {
- return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
- }
+ return $event->isValid;
+ }
- /**
- * Returns the route of the current request.
- * @return string the route (module ID, controller ID and action ID) of the current request.
- */
- public function getRoute()
- {
- return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId();
- }
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * If you override this method, please make sure you call the parent implementation first.
+ * Also make sure you return the action result, whether it is processed or not.
+ * @param Action $action the action just executed.
+ * @param mixed $result the action return result.
+ * @return mixed the processed action result.
+ */
+ public function afterAction($action, $result)
+ {
+ $event = new ActionEvent($action);
+ $event->result = $result;
+ $this->trigger(self::EVENT_AFTER_ACTION, $event);
- /**
- * Renders a view and applies layout if available.
- *
- * The view to be rendered can be specified in one of the following formats:
- *
- * - path alias (e.g. "@app/views/site/index");
- * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
- * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
- * - absolute path within 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]].
- * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
- *
- * To determine which layout should be applied, the following two steps are conducted:
- *
- * 1. In the first step, it determines the layout name and the context module:
- *
- * - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
- * - If [[layout]] is null, search through all ancestor modules of this controller and find the first
- * module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
- * are used as the layout name and the context module, respectively. If such a module is not found
- * or the corresponding layout is not a string, it will return false, meaning no applicable layout.
- *
- * 2. In the second step, it determines the actual layout file according to the previously found layout name
- * and context module. The layout name can be:
- *
- * - a path alias (e.g. "@app/views/layouts/main");
- * - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
- * looked for under the [[Application::layoutPath|layout path]] of the application;
- * - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
- * [[Module::layoutPath|layout path]] of the context module.
- *
- * 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 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.
- * @throws InvalidParamException if the view file or the layout file does not exist.
- */
- public function render($view, $params = [])
- {
- $output = $this->getView()->render($view, $params, $this);
- $layoutFile = $this->findLayoutFile($this->getView());
- if ($layoutFile !== false) {
- return $this->getView()->renderFile($layoutFile, ['content' => $output], $this);
- } else {
- return $output;
- }
- }
+ return $event->result;
+ }
- /**
- * Renders a view.
- * This method differs from [[render()]] in that it does not apply any layout.
- * @param string $view the view name. Please refer to [[render()]] on how to specify a 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.
- */
- public function renderPartial($view, $params = [])
- {
- return $this->getView()->render($view, $params, $this);
- }
+ /**
+ * @return string the controller ID that is prefixed with the module ID (if any).
+ */
+ public function getUniqueId()
+ {
+ return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
+ }
- /**
- * Renders a view file.
- * @param string $file the view file to be rendered. This can be either a file path or a path alias.
- * @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.
- */
- public function renderFile($file, $params = [])
- {
- return $this->getView()->renderFile($file, $params, $this);
- }
+ /**
+ * Returns the route of the current request.
+ * @return string the route (module ID, controller ID and action ID) of the current request.
+ */
+ public function getRoute()
+ {
+ return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId();
+ }
- /**
- * Returns the view object that can be used to render views or view files.
- * The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
- * this view object to implement the actual view rendering.
- * If not set, it will default to the "view" application component.
- * @return View the view object that can be used to render views or view files.
- */
- public function getView()
- {
- if ($this->_view === null) {
- $this->_view = Yii::$app->getView();
- }
- return $this->_view;
- }
+ /**
+ * Renders a view and applies layout if available.
+ *
+ * The view to be rendered can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
+ * - absolute path within 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]].
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ *
+ * To determine which layout should be applied, the following two steps are conducted:
+ *
+ * 1. In the first step, it determines the layout name and the context module:
+ *
+ * - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
+ * - If [[layout]] is null, search through all ancestor modules of this controller and find the first
+ * module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
+ * are used as the layout name and the context module, respectively. If such a module is not found
+ * or the corresponding layout is not a string, it will return false, meaning no applicable layout.
+ *
+ * 2. In the second step, it determines the actual layout file according to the previously found layout name
+ * and context module. The layout name can be:
+ *
+ * - a path alias (e.g. "@app/views/layouts/main");
+ * - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
+ * looked for under the [[Application::layoutPath|layout path]] of the application;
+ * - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
+ * [[Module::layoutPath|layout path]] of the context module.
+ *
+ * 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 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.
+ * @throws InvalidParamException if the view file or the layout file does not exist.
+ */
+ public function render($view, $params = [])
+ {
+ $output = $this->getView()->render($view, $params, $this);
+ $layoutFile = $this->findLayoutFile($this->getView());
+ if ($layoutFile !== false) {
+ return $this->getView()->renderFile($layoutFile, ['content' => $output], $this);
+ } else {
+ return $output;
+ }
+ }
- /**
- * Sets the view object to be used by this controller.
- * @param View $view the view object that can be used to render views or view files.
- */
- public function setView($view)
- {
- $this->_view = $view;
- }
+ /**
+ * Renders a view.
+ * This method differs from [[render()]] in that it does not apply any layout.
+ * @param string $view the view name. Please refer to [[render()]] on how to specify a 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.
+ */
+ public function renderPartial($view, $params = [])
+ {
+ return $this->getView()->render($view, $params, $this);
+ }
- /**
- * Returns the directory containing view files for this controller.
- * The default implementation returns the directory named as controller [[id]] under the [[module]]'s
- * [[viewPath]] directory.
- * @return string the directory containing the view files for this controller.
- */
- public function getViewPath()
- {
- return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
- }
+ /**
+ * Renders a view file.
+ * @param string $file the view file to be rendered. This can be either a file path or a path alias.
+ * @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.
+ */
+ public function renderFile($file, $params = [])
+ {
+ return $this->getView()->renderFile($file, $params, $this);
+ }
- /**
- * 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;
- }
+ /**
+ * Returns the view object that can be used to render views or view files.
+ * The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
+ * this view object to implement the actual view rendering.
+ * If not set, it will default to the "view" application component.
+ * @return View the view object that can be used to render views or view files.
+ */
+ public function getView()
+ {
+ if ($this->_view === null) {
+ $this->_view = Yii::$app->getView();
+ }
- /**
- * 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.
- * Please refer to [[render()]] on how to specify this parameter.
- * @throws InvalidParamException if an invalid path alias is used to specify the layout.
- */
- protected function findLayoutFile($view)
- {
- $module = $this->module;
- if (is_string($this->layout)) {
- $layout = $this->layout;
- } elseif ($this->layout === null) {
- while ($module !== null && $module->layout === null) {
- $module = $module->module;
- }
- if ($module !== null && is_string($module->layout)) {
- $layout = $module->layout;
- }
- }
+ return $this->_view;
+ }
- if (!isset($layout)) {
- return false;
- }
+ /**
+ * Sets the view object to be used by this controller.
+ * @param View $view the view object that can be used to render views or view files.
+ */
+ public function setView($view)
+ {
+ $this->_view = $view;
+ }
- if (strncmp($layout, '@', 1) === 0) {
- $file = Yii::getAlias($layout);
- } elseif (strncmp($layout, '/', 1) === 0) {
- $file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . substr($layout, 1);
- } else {
- $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout;
- }
+ /**
+ * Returns the directory containing view files for this controller.
+ * The default implementation returns the directory named as controller [[id]] under the [[module]]'s
+ * [[viewPath]] directory.
+ * @return string the directory containing the view files for this controller.
+ */
+ public function getViewPath()
+ {
+ return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
+ }
- if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
- return $file;
- }
- $path = $file . '.' . $view->defaultExtension;
- if ($view->defaultExtension !== 'php' && !is_file($path)) {
- $path = $file . '.php';
- }
- return $path;
- }
+ /**
+ * 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.
+ * Please refer to [[render()]] on how to specify this parameter.
+ * @throws InvalidParamException if an invalid path alias is used to specify the layout.
+ */
+ protected function findLayoutFile($view)
+ {
+ $module = $this->module;
+ if (is_string($this->layout)) {
+ $layout = $this->layout;
+ } elseif ($this->layout === null) {
+ while ($module !== null && $module->layout === null) {
+ $module = $module->module;
+ }
+ if ($module !== null && is_string($module->layout)) {
+ $layout = $module->layout;
+ }
+ }
+
+ if (!isset($layout)) {
+ return false;
+ }
+
+ if (strncmp($layout, '@', 1) === 0) {
+ $file = Yii::getAlias($layout);
+ } elseif (strncmp($layout, '/', 1) === 0) {
+ $file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . substr($layout, 1);
+ } else {
+ $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout;
+ }
+
+ if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
+ return $file;
+ }
+ $path = $file . '.' . $view->defaultExtension;
+ if ($view->defaultExtension !== 'php' && !is_file($path)) {
+ $path = $file . '.php';
+ }
+
+ return $path;
+ }
}
diff --git a/framework/base/DynamicModel.php b/framework/base/DynamicModel.php
index b93648da046..afe94f4c06b 100644
--- a/framework/base/DynamicModel.php
+++ b/framework/base/DynamicModel.php
@@ -55,143 +55,145 @@
*/
class DynamicModel extends Model
{
- private $_attributes = [];
+ private $_attributes = [];
- /**
- * Constructors.
- * @param array $attributes the dynamic attributes (name-value pairs, or names) being defined
- * @param array $config the configuration array to be applied to this object.
- */
- public function __construct(array $attributes = [], $config = [])
- {
- foreach ($attributes as $name => $value) {
- if (is_integer($name)) {
- $this->_attributes[$value] = null;
- } else {
- $this->_attributes[$name] = $value;
- }
- }
- }
+ /**
+ * Constructors.
+ * @param array $attributes the dynamic attributes (name-value pairs, or names) being defined
+ * @param array $config the configuration array to be applied to this object.
+ */
+ public function __construct(array $attributes = [], $config = [])
+ {
+ foreach ($attributes as $name => $value) {
+ if (is_integer($name)) {
+ $this->_attributes[$value] = null;
+ } else {
+ $this->_attributes[$name] = $value;
+ }
+ }
+ }
- /**
- * @inheritdoc
- */
- public function __get($name)
- {
- if (array_key_exists($name, $this->_attributes)) {
- return $this->_attributes[$name];
- } else {
- return parent::__get($name);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function __get($name)
+ {
+ if (array_key_exists($name, $this->_attributes)) {
+ return $this->_attributes[$name];
+ } else {
+ return parent::__get($name);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function __set($name, $value)
- {
- if (array_key_exists($name, $this->_attributes)) {
- $this->_attributes[$name] = $value;
- } else {
- parent::__set($name, $value);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function __set($name, $value)
+ {
+ if (array_key_exists($name, $this->_attributes)) {
+ $this->_attributes[$name] = $value;
+ } else {
+ parent::__set($name, $value);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function __isset($name)
- {
- if (array_key_exists($name, $this->_attributes)) {
- return isset($this->_attributes[$name]);
- } else {
- return parent::__isset($name);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function __isset($name)
+ {
+ if (array_key_exists($name, $this->_attributes)) {
+ return isset($this->_attributes[$name]);
+ } else {
+ return parent::__isset($name);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function __unset($name)
- {
- if (array_key_exists($name, $this->_attributes)) {
- unset($this->_attributes[$name]);
- } else {
- parent::__unset($name);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function __unset($name)
+ {
+ if (array_key_exists($name, $this->_attributes)) {
+ unset($this->_attributes[$name]);
+ } else {
+ parent::__unset($name);
+ }
+ }
- /**
- * Defines an attribute.
- * @param string $name the attribute name
- * @param mixed $value the attribute value
- */
- public function defineAttribute($name, $value = null)
- {
- $this->_attributes[$name] = $value;
- }
+ /**
+ * Defines an attribute.
+ * @param string $name the attribute name
+ * @param mixed $value the attribute value
+ */
+ public function defineAttribute($name, $value = null)
+ {
+ $this->_attributes[$name] = $value;
+ }
- /**
- * Undefines an attribute.
- * @param string $name the attribute name
- */
- public function undefineAttribute($name)
- {
- unset($this->_attributes[$name]);
- }
+ /**
+ * Undefines an attribute.
+ * @param string $name the attribute name
+ */
+ public function undefineAttribute($name)
+ {
+ unset($this->_attributes[$name]);
+ }
- /**
- * Adds a validation rule to this model.
- * You can also directly manipulate [[validators]] to add or remove validation rules.
- * This method provides a shortcut.
- * @param string|array $attributes the attribute(s) to be validated by the rule
- * @param mixed $validator the validator for the rule.This can be a built-in validator name,
- * a method name of the model class, an anonymous function, or a validator class name.
- * @param array $options the options (name-value pairs) to be applied to the validator
- * @return static the model itself
- */
- public function addRule($attributes, $validator, $options = [])
- {
- $validators = $this->getValidators();
- $validators->append(Validator::createValidator($validator, $this, (array)$attributes, $options));
- return $this;
- }
+ /**
+ * Adds a validation rule to this model.
+ * You can also directly manipulate [[validators]] to add or remove validation rules.
+ * This method provides a shortcut.
+ * @param string|array $attributes the attribute(s) to be validated by the rule
+ * @param mixed $validator the validator for the rule.This can be a built-in validator name,
+ * a method name of the model class, an anonymous function, or a validator class name.
+ * @param array $options the options (name-value pairs) to be applied to the validator
+ * @return static the model itself
+ */
+ public function addRule($attributes, $validator, $options = [])
+ {
+ $validators = $this->getValidators();
+ $validators->append(Validator::createValidator($validator, $this, (array) $attributes, $options));
- /**
- * Validates the given data with the specified validation rules.
- * This method will create a DynamicModel instance, populate it with the data to be validated,
- * create the specified validation rules, and then validate the data using these rules.
- * @param array $data the data (name-value pairs) to be validated
- * @param array $rules the validation rules. Please refer to [[Model::rules()]] on the format of this parameter.
- * @return static the model instance that contains the data being validated
- * @throws InvalidConfigException if a validation rule is not specified correctly.
- */
- public static function validateData(array $data, $rules = [])
- {
- /** @var DynamicModel $model */
- $model = new static($data);
- if (!empty($rules)) {
- $validators = $model->getValidators();
- foreach ($rules as $rule) {
- if ($rule instanceof Validator) {
- $validators->append($rule);
- } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
- $validator = Validator::createValidator($rule[1], $model, (array) $rule[0], array_slice($rule, 2));
- $validators->append($validator);
- } else {
- throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
- }
- }
- $model->validate();
- }
- return $model;
- }
+ return $this;
+ }
- /**
- * @inheritdoc
- */
- public function attributes()
- {
- return array_keys($this->_attributes);
- }
+ /**
+ * Validates the given data with the specified validation rules.
+ * This method will create a DynamicModel instance, populate it with the data to be validated,
+ * create the specified validation rules, and then validate the data using these rules.
+ * @param array $data the data (name-value pairs) to be validated
+ * @param array $rules the validation rules. Please refer to [[Model::rules()]] on the format of this parameter.
+ * @return static the model instance that contains the data being validated
+ * @throws InvalidConfigException if a validation rule is not specified correctly.
+ */
+ public static function validateData(array $data, $rules = [])
+ {
+ /** @var DynamicModel $model */
+ $model = new static($data);
+ if (!empty($rules)) {
+ $validators = $model->getValidators();
+ foreach ($rules as $rule) {
+ if ($rule instanceof Validator) {
+ $validators->append($rule);
+ } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
+ $validator = Validator::createValidator($rule[1], $model, (array) $rule[0], array_slice($rule, 2));
+ $validators->append($validator);
+ } else {
+ throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
+ }
+ }
+ $model->validate();
+ }
+
+ return $model;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributes()
+ {
+ return array_keys($this->_attributes);
+ }
}
diff --git a/framework/base/ErrorException.php b/framework/base/ErrorException.php
index 5f1e9997601..67e825567ff 100644
--- a/framework/base/ErrorException.php
+++ b/framework/base/ErrorException.php
@@ -17,80 +17,81 @@
*/
class ErrorException extends \ErrorException
{
- /**
- * Constructs the exception.
- * @link http://php.net/manual/en/errorexception.construct.php
- * @param $message [optional]
- * @param $code [optional]
- * @param $severity [optional]
- * @param $filename [optional]
- * @param $lineno [optional]
- * @param $previous [optional]
- */
- public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null)
- {
- parent::__construct($message, $code, $previous);
- $this->severity = $severity;
- $this->file = $filename;
- $this->line = $lineno;
+ /**
+ * Constructs the exception.
+ * @link http://php.net/manual/en/errorexception.construct.php
+ * @param $message [optional]
+ * @param $code [optional]
+ * @param $severity [optional]
+ * @param $filename [optional]
+ * @param $lineno [optional]
+ * @param $previous [optional]
+ */
+ public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ $this->severity = $severity;
+ $this->file = $filename;
+ $this->line = $lineno;
- if (function_exists('xdebug_get_function_stack')) {
- $trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1);
- foreach ($trace as &$frame) {
- if (!isset($frame['function'])) {
- $frame['function'] = 'unknown';
- }
+ if (function_exists('xdebug_get_function_stack')) {
+ $trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1);
+ foreach ($trace as &$frame) {
+ if (!isset($frame['function'])) {
+ $frame['function'] = 'unknown';
+ }
- // XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
- if (!isset($frame['type']) || $frame['type'] === 'static') {
- $frame['type'] = '::';
- } elseif ($frame['type'] === 'dynamic') {
- $frame['type'] = '->';
- }
+ // XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
+ if (!isset($frame['type']) || $frame['type'] === 'static') {
+ $frame['type'] = '::';
+ } elseif ($frame['type'] === 'dynamic') {
+ $frame['type'] = '->';
+ }
- // XDebug has a different key name
- if (isset($frame['params']) && !isset($frame['args'])) {
- $frame['args'] = $frame['params'];
- }
- }
+ // XDebug has a different key name
+ if (isset($frame['params']) && !isset($frame['args'])) {
+ $frame['args'] = $frame['params'];
+ }
+ }
- $ref = new \ReflectionProperty('Exception', 'trace');
- $ref->setAccessible(true);
- $ref->setValue($this, $trace);
- }
- }
+ $ref = new \ReflectionProperty('Exception', 'trace');
+ $ref->setAccessible(true);
+ $ref->setValue($this, $trace);
+ }
+ }
- /**
- * Returns if error is one of fatal type.
- *
- * @param array $error error got from error_get_last()
- * @return boolean if error is one of fatal type
- */
- public static function isFatalError($error)
- {
- return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING]);
- }
+ /**
+ * Returns if error is one of fatal type.
+ *
+ * @param array $error error got from error_get_last()
+ * @return boolean if error is one of fatal type
+ */
+ public static function isFatalError($error)
+ {
+ return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING]);
+ }
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- $names = [
- E_ERROR => 'PHP Fatal Error',
- E_PARSE => 'PHP Parse Error',
- E_CORE_ERROR => 'PHP Core Error',
- E_COMPILE_ERROR => 'PHP Compile Error',
- E_USER_ERROR => 'PHP User Error',
- E_WARNING => 'PHP Warning',
- E_CORE_WARNING => 'PHP Core Warning',
- E_COMPILE_WARNING => 'PHP Compile Warning',
- E_USER_WARNING => 'PHP User Warning',
- E_STRICT => 'PHP Strict Warning',
- E_NOTICE => 'PHP Notice',
- E_RECOVERABLE_ERROR => 'PHP Recoverable Error',
- E_DEPRECATED => 'PHP Deprecated Warning',
- ];
- return isset($names[$this->getCode()]) ? $names[$this->getCode()] : 'Error';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ $names = [
+ E_ERROR => 'PHP Fatal Error',
+ E_PARSE => 'PHP Parse Error',
+ E_CORE_ERROR => 'PHP Core Error',
+ E_COMPILE_ERROR => 'PHP Compile Error',
+ E_USER_ERROR => 'PHP User Error',
+ E_WARNING => 'PHP Warning',
+ E_CORE_WARNING => 'PHP Core Warning',
+ E_COMPILE_WARNING => 'PHP Compile Warning',
+ E_USER_WARNING => 'PHP User Warning',
+ E_STRICT => 'PHP Strict Warning',
+ E_NOTICE => 'PHP Notice',
+ E_RECOVERABLE_ERROR => 'PHP Recoverable Error',
+ E_DEPRECATED => 'PHP Deprecated Warning',
+ ];
+
+ return isset($names[$this->getCode()]) ? $names[$this->getCode()] : 'Error';
+ }
}
diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php
index 528c5ab1673..fce20b61d5e 100644
--- a/framework/base/ErrorHandler.php
+++ b/framework/base/ErrorHandler.php
@@ -25,324 +25,329 @@
*/
class ErrorHandler extends Component
{
- /**
- * @var integer maximum number of source code lines to be displayed. Defaults to 25.
- */
- public $maxSourceLines = 25;
- /**
- * @var integer maximum number of trace source code lines to be displayed. Defaults to 10.
- */
- public $maxTraceSourceLines = 10;
- /**
- * @var boolean whether to discard any existing page output before error display. Defaults to true.
- */
- public $discardExistingOutput = true;
- /**
- * @var string the route (e.g. 'site/error') to the controller action that will be used
- * to display external errors. Inside the action, it can retrieve the error information
- * by Yii::$app->exception. This property defaults to null, meaning ErrorHandler
- * will handle the error display.
- */
- public $errorAction;
- /**
- * @var string the path of the view file for rendering exceptions without call stack information.
- */
- public $errorView = '@yii/views/errorHandler/error.php';
- /**
- * @var string the path of the view file for rendering exceptions.
- */
- public $exceptionView = '@yii/views/errorHandler/exception.php';
- /**
- * @var string the path of the view file for rendering exceptions and errors call stack element.
- */
- public $callStackItemView = '@yii/views/errorHandler/callStackItem.php';
- /**
- * @var string the path of the view file for rendering previous exceptions.
- */
- public $previousExceptionView = '@yii/views/errorHandler/previousException.php';
- /**
- * @var \Exception the exception that is being handled currently.
- */
- public $exception;
+ /**
+ * @var integer maximum number of source code lines to be displayed. Defaults to 25.
+ */
+ public $maxSourceLines = 25;
+ /**
+ * @var integer maximum number of trace source code lines to be displayed. Defaults to 10.
+ */
+ public $maxTraceSourceLines = 10;
+ /**
+ * @var boolean whether to discard any existing page output before error display. Defaults to true.
+ */
+ public $discardExistingOutput = true;
+ /**
+ * @var string the route (e.g. 'site/error') to the controller action that will be used
+ * to display external errors. Inside the action, it can retrieve the error information
+ * by Yii::$app->exception. This property defaults to null, meaning ErrorHandler
+ * will handle the error display.
+ */
+ public $errorAction;
+ /**
+ * @var string the path of the view file for rendering exceptions without call stack information.
+ */
+ public $errorView = '@yii/views/errorHandler/error.php';
+ /**
+ * @var string the path of the view file for rendering exceptions.
+ */
+ public $exceptionView = '@yii/views/errorHandler/exception.php';
+ /**
+ * @var string the path of the view file for rendering exceptions and errors call stack element.
+ */
+ public $callStackItemView = '@yii/views/errorHandler/callStackItem.php';
+ /**
+ * @var string the path of the view file for rendering previous exceptions.
+ */
+ public $previousExceptionView = '@yii/views/errorHandler/previousException.php';
+ /**
+ * @var \Exception the exception that is being handled currently.
+ */
+ public $exception;
+ /**
+ * Handles exception.
+ * @param \Exception $exception to be handled.
+ */
+ public function handle($exception)
+ {
+ $this->exception = $exception;
+ if ($this->discardExistingOutput) {
+ $this->clearOutput();
+ }
+ $this->renderException($exception);
+ }
- /**
- * Handles exception.
- * @param \Exception $exception to be handled.
- */
- public function handle($exception)
- {
- $this->exception = $exception;
- if ($this->discardExistingOutput) {
- $this->clearOutput();
- }
- $this->renderException($exception);
- }
+ /**
+ * Renders the exception.
+ * @param \Exception $exception the exception to be handled.
+ */
+ protected function renderException($exception)
+ {
+ if (Yii::$app instanceof \yii\console\Application || YII_ENV_TEST) {
+ echo Yii::$app->renderException($exception);
+ if (!YII_ENV_TEST) {
+ exit(1);
+ }
- /**
- * Renders the exception.
- * @param \Exception $exception the exception to be handled.
- */
- protected function renderException($exception)
- {
- if (Yii::$app instanceof \yii\console\Application || YII_ENV_TEST) {
- echo Yii::$app->renderException($exception);
- if (!YII_ENV_TEST) {
- exit(1);
- }
- return;
- }
+ return;
+ }
- $response = Yii::$app->getResponse();
+ $response = Yii::$app->getResponse();
- $useErrorView = $response->format === \yii\web\Response::FORMAT_HTML && (!YII_DEBUG || $exception instanceof UserException);
+ $useErrorView = $response->format === \yii\web\Response::FORMAT_HTML && (!YII_DEBUG || $exception instanceof UserException);
- if ($useErrorView && $this->errorAction !== null) {
- $result = Yii::$app->runAction($this->errorAction);
- if ($result instanceof Response) {
- $response = $result;
- } else {
- $response->data = $result;
- }
- } elseif ($response->format === \yii\web\Response::FORMAT_HTML) {
- if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
- // AJAX request
- $response->data = Yii::$app->renderException($exception);
- } else {
- // if there is an error during error rendering it's useful to
- // display PHP error in debug mode instead of a blank screen
- if (YII_DEBUG) {
- ini_set('display_errors', 1);
- }
- $file = $useErrorView ? $this->errorView : $this->exceptionView;
- $response->data = $this->renderFile($file, [
- 'exception' => $exception,
- ]);
- }
- } else {
- $response->data = $this->convertExceptionToArray($exception);
- }
+ if ($useErrorView && $this->errorAction !== null) {
+ $result = Yii::$app->runAction($this->errorAction);
+ if ($result instanceof Response) {
+ $response = $result;
+ } else {
+ $response->data = $result;
+ }
+ } elseif ($response->format === \yii\web\Response::FORMAT_HTML) {
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
+ // AJAX request
+ $response->data = Yii::$app->renderException($exception);
+ } else {
+ // if there is an error during error rendering it's useful to
+ // display PHP error in debug mode instead of a blank screen
+ if (YII_DEBUG) {
+ ini_set('display_errors', 1);
+ }
+ $file = $useErrorView ? $this->errorView : $this->exceptionView;
+ $response->data = $this->renderFile($file, [
+ 'exception' => $exception,
+ ]);
+ }
+ } else {
+ $response->data = $this->convertExceptionToArray($exception);
+ }
- if ($exception instanceof HttpException) {
- $response->setStatusCode($exception->statusCode);
- } else {
- $response->setStatusCode(500);
- }
+ if ($exception instanceof HttpException) {
+ $response->setStatusCode($exception->statusCode);
+ } else {
+ $response->setStatusCode(500);
+ }
- $response->send();
- }
+ $response->send();
+ }
- /**
- * Converts an exception into an array.
- * @param \Exception $exception the exception being converted
- * @return array the array representation of the exception.
- */
- protected function convertExceptionToArray($exception)
- {
- $array = [
- 'type' => get_class($exception),
- 'name' => $exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : 'Exception',
- 'message' => $exception->getMessage(),
- 'code' => $exception->getCode(),
- ];
- if ($exception instanceof HttpException) {
- $array['status'] = $exception->statusCode;
- }
- if (($prev = $exception->getPrevious()) !== null) {
- $array['previous'] = $this->convertExceptionToArray($prev);
- }
- return $array;
- }
+ /**
+ * Converts an exception into an array.
+ * @param \Exception $exception the exception being converted
+ * @return array the array representation of the exception.
+ */
+ protected function convertExceptionToArray($exception)
+ {
+ $array = [
+ 'type' => get_class($exception),
+ 'name' => $exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : 'Exception',
+ 'message' => $exception->getMessage(),
+ 'code' => $exception->getCode(),
+ ];
+ if ($exception instanceof HttpException) {
+ $array['status'] = $exception->statusCode;
+ }
+ if (($prev = $exception->getPrevious()) !== null) {
+ $array['previous'] = $this->convertExceptionToArray($prev);
+ }
- /**
- * Converts special characters to HTML entities.
- * @param string $text to encode.
- * @return string encoded original text.
- */
- public function htmlEncode($text)
- {
- return htmlspecialchars($text, ENT_QUOTES, Yii::$app->charset);
- }
+ return $array;
+ }
- /**
- * Removes all output echoed before calling this method.
- */
- public function clearOutput()
- {
- // the following manual level counting is to deal with zlib.output_compression set to On
- for ($level = ob_get_level(); $level > 0; --$level) {
- if (!@ob_end_clean()) {
- ob_clean();
- }
- }
- }
+ /**
+ * Converts special characters to HTML entities.
+ * @param string $text to encode.
+ * @return string encoded original text.
+ */
+ public function htmlEncode($text)
+ {
+ return htmlspecialchars($text, ENT_QUOTES, Yii::$app->charset);
+ }
- /**
- * Adds informational links to the given PHP type/class.
- * @param string $code type/class name to be linkified.
- * @return string linkified with HTML type/class name.
- */
- public function addTypeLinks($code)
- {
- $html = '';
- if (strpos($code, '\\') !== false) {
- // namespaced class
- foreach (explode('\\', $code) as $part) {
- $html .= '' . $this->htmlEncode($part) . '\\';
- }
- $html = rtrim($html, '\\');
- } elseif (strpos($code, '()') !== false) {
- // method/function call
- $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) {
- return '' .
- $this->htmlEncode($matches[1]) . '()';
- }, $code);
- }
- return $html;
- }
+ /**
+ * Removes all output echoed before calling this method.
+ */
+ public function clearOutput()
+ {
+ // the following manual level counting is to deal with zlib.output_compression set to On
+ for ($level = ob_get_level(); $level > 0; --$level) {
+ if (!@ob_end_clean()) {
+ ob_clean();
+ }
+ }
+ }
- /**
- * Renders a view file as a PHP script.
- * @param string $_file_ the view file.
- * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
- * @return string the rendering result
- */
- public function renderFile($_file_, $_params_)
- {
- $_params_['handler'] = $this;
- if ($this->exception instanceof ErrorException) {
- ob_start();
- ob_implicit_flush(false);
- extract($_params_, EXTR_OVERWRITE);
- require(Yii::getAlias($_file_));
- return ob_get_clean();
- } else {
- return Yii::$app->getView()->renderFile($_file_, $_params_, $this);
- }
- }
+ /**
+ * Adds informational links to the given PHP type/class.
+ * @param string $code type/class name to be linkified.
+ * @return string linkified with HTML type/class name.
+ */
+ public function addTypeLinks($code)
+ {
+ $html = '';
+ if (strpos($code, '\\') !== false) {
+ // namespaced class
+ foreach (explode('\\', $code) as $part) {
+ $html .= '' . $this->htmlEncode($part) . '\\';
+ }
+ $html = rtrim($html, '\\');
+ } elseif (strpos($code, '()') !== false) {
+ // method/function call
+ $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) {
+ return '' .
+ $this->htmlEncode($matches[1]) . '()';
+ }, $code);
+ }
- /**
- * Renders the previous exception stack for a given Exception.
- * @param \Exception $exception the exception whose precursors should be rendered.
- * @return string HTML content of the rendered previous exceptions.
- * Empty string if there are none.
- */
- public function renderPreviousExceptions($exception)
- {
- if (($previous = $exception->getPrevious()) !== null) {
- return $this->renderFile($this->previousExceptionView, ['exception' => $previous]);
- } else {
- return '';
- }
- }
+ return $html;
+ }
- /**
- * Renders a single call stack element.
- * @param string|null $file name where call has happened.
- * @param integer|null $line number on which call has happened.
- * @param string|null $class called class name.
- * @param string|null $method called function/method name.
- * @param integer $index number of the call stack element.
- * @return string HTML content of the rendered call stack element.
- */
- public function renderCallStackItem($file, $line, $class, $method, $index)
- {
- $lines = [];
- $begin = $end = 0;
- if ($file !== null && $line !== null) {
- $line--; // adjust line number from one-based to zero-based
- $lines = @file($file);
- if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line + 1) {
- return '';
- }
+ /**
+ * Renders a view file as a PHP script.
+ * @param string $_file_ the view file.
+ * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @return string the rendering result
+ */
+ public function renderFile($_file_, $_params_)
+ {
+ $_params_['handler'] = $this;
+ if ($this->exception instanceof ErrorException) {
+ ob_start();
+ ob_implicit_flush(false);
+ extract($_params_, EXTR_OVERWRITE);
+ require(Yii::getAlias($_file_));
- $half = (int)(($index == 0 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2);
- $begin = $line - $half > 0 ? $line - $half : 0;
- $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1;
- }
+ return ob_get_clean();
+ } else {
+ return Yii::$app->getView()->renderFile($_file_, $_params_, $this);
+ }
+ }
- return $this->renderFile($this->callStackItemView, [
- 'file' => $file,
- 'line' => $line,
- 'class' => $class,
- 'method' => $method,
- 'index' => $index,
- 'lines' => $lines,
- 'begin' => $begin,
- 'end' => $end,
- ]);
- }
+ /**
+ * Renders the previous exception stack for a given Exception.
+ * @param \Exception $exception the exception whose precursors should be rendered.
+ * @return string HTML content of the rendered previous exceptions.
+ * Empty string if there are none.
+ */
+ public function renderPreviousExceptions($exception)
+ {
+ if (($previous = $exception->getPrevious()) !== null) {
+ return $this->renderFile($this->previousExceptionView, ['exception' => $previous]);
+ } else {
+ return '';
+ }
+ }
- /**
- * Renders the request information.
- * @return string the rendering result
- */
- public function renderRequest()
- {
- $request = '';
- foreach (['_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV'] as $name) {
- if (!empty($GLOBALS[$name])) {
- $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n";
- }
- }
- return '
' . rtrim($request, "\n") . '
';
- }
+ /**
+ * Renders a single call stack element.
+ * @param string|null $file name where call has happened.
+ * @param integer|null $line number on which call has happened.
+ * @param string|null $class called class name.
+ * @param string|null $method called function/method name.
+ * @param integer $index number of the call stack element.
+ * @return string HTML content of the rendered call stack element.
+ */
+ public function renderCallStackItem($file, $line, $class, $method, $index)
+ {
+ $lines = [];
+ $begin = $end = 0;
+ if ($file !== null && $line !== null) {
+ $line--; // adjust line number from one-based to zero-based
+ $lines = @file($file);
+ if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line + 1) {
+ return '';
+ }
- /**
- * Determines whether given name of the file belongs to the framework.
- * @param string $file name to be checked.
- * @return boolean whether given name of the file belongs to the framework.
- */
- public function isCoreFile($file)
- {
- return $file === null || strpos(realpath($file), YII_PATH . DIRECTORY_SEPARATOR) === 0;
- }
+ $half = (int) (($index == 0 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2);
+ $begin = $line - $half > 0 ? $line - $half : 0;
+ $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1;
+ }
- /**
- * Creates HTML containing link to the page with the information on given HTTP status code.
- * @param integer $statusCode to be used to generate information link.
- * @param string $statusDescription Description to display after the the status code.
- * @return string generated HTML with HTTP status code information.
- */
- public function createHttpStatusLink($statusCode, $statusDescription)
- {
- return 'HTTP ' . (int)$statusCode . ' – ' . $statusDescription . '';
- }
+ return $this->renderFile($this->callStackItemView, [
+ 'file' => $file,
+ 'line' => $line,
+ 'class' => $class,
+ 'method' => $method,
+ 'index' => $index,
+ 'lines' => $lines,
+ 'begin' => $begin,
+ 'end' => $end,
+ ]);
+ }
- /**
- * Creates string containing HTML link which refers to the home page of determined web-server software
- * and its full name.
- * @return string server software information hyperlink.
- */
- public function createServerInformationLink()
- {
- static $serverUrls = [
- 'http://httpd.apache.org/' => ['apache'],
- 'http://nginx.org/' => ['nginx'],
- 'http://lighttpd.net/' => ['lighttpd'],
- 'http://gwan.com/' => ['g-wan', 'gwan'],
- 'http://iis.net/' => ['iis', 'services'],
- 'http://php.net/manual/en/features.commandline.webserver.php' => ['development'],
- ];
- if (isset($_SERVER['SERVER_SOFTWARE'])) {
- foreach ($serverUrls as $url => $keywords) {
- foreach ($keywords as $keyword) {
- if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
- return '' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '';
- }
- }
- }
- }
- return '';
- }
+ /**
+ * Renders the request information.
+ * @return string the rendering result
+ */
+ public function renderRequest()
+ {
+ $request = '';
+ foreach (['_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV'] as $name) {
+ if (!empty($GLOBALS[$name])) {
+ $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n";
+ }
+ }
- /**
- * Creates string containing HTML link which refers to the page with the current version
- * of the framework and version number text.
- * @return string framework version information hyperlink.
- */
- public function createFrameworkVersionLink()
- {
- return '' . $this->htmlEncode(Yii::getVersion()) . '';
- }
+ return '
' . rtrim($request, "\n") . '
';
+ }
+
+ /**
+ * Determines whether given name of the file belongs to the framework.
+ * @param string $file name to be checked.
+ * @return boolean whether given name of the file belongs to the framework.
+ */
+ public function isCoreFile($file)
+ {
+ return $file === null || strpos(realpath($file), YII_PATH . DIRECTORY_SEPARATOR) === 0;
+ }
+
+ /**
+ * Creates HTML containing link to the page with the information on given HTTP status code.
+ * @param integer $statusCode to be used to generate information link.
+ * @param string $statusDescription Description to display after the the status code.
+ * @return string generated HTML with HTTP status code information.
+ */
+ public function createHttpStatusLink($statusCode, $statusDescription)
+ {
+ return 'HTTP ' . (int) $statusCode . ' – ' . $statusDescription . '';
+ }
+
+ /**
+ * Creates string containing HTML link which refers to the home page of determined web-server software
+ * and its full name.
+ * @return string server software information hyperlink.
+ */
+ public function createServerInformationLink()
+ {
+ static $serverUrls = [
+ 'http://httpd.apache.org/' => ['apache'],
+ 'http://nginx.org/' => ['nginx'],
+ 'http://lighttpd.net/' => ['lighttpd'],
+ 'http://gwan.com/' => ['g-wan', 'gwan'],
+ 'http://iis.net/' => ['iis', 'services'],
+ 'http://php.net/manual/en/features.commandline.webserver.php' => ['development'],
+ ];
+ if (isset($_SERVER['SERVER_SOFTWARE'])) {
+ foreach ($serverUrls as $url => $keywords) {
+ foreach ($keywords as $keyword) {
+ if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
+ return '' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '';
+ }
+ }
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Creates string containing HTML link which refers to the page with the current version
+ * of the framework and version number text.
+ * @return string framework version information hyperlink.
+ */
+ public function createFrameworkVersionLink()
+ {
+ return '' . $this->htmlEncode(Yii::getVersion()) . '';
+ }
}
diff --git a/framework/base/Event.php b/framework/base/Event.php
index 1a83467daf8..def96818540 100644
--- a/framework/base/Event.php
+++ b/framework/base/Event.php
@@ -24,162 +24,165 @@
*/
class Event extends Object
{
- /**
- * @var string the event name. This property is set by [[Component::trigger()]] and [[trigger()]].
- * Event handlers may use this property to check what event it is handling.
- */
- public $name;
- /**
- * @var object the sender of this event. If not set, this property will be
- * set as the object whose "trigger()" method is called.
- * This property may also be a `null` when this event is a
- * class-level event which is triggered in a static context.
- */
- public $sender;
- /**
- * @var boolean whether the event is handled. Defaults to false.
- * When a handler sets this to be true, the event processing will stop and
- * ignore the rest of the uninvoked event handlers.
- */
- public $handled = false;
- /**
- * @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
- * Note that this varies according to which event handler is currently executing.
- */
- public $data;
+ /**
+ * @var string the event name. This property is set by [[Component::trigger()]] and [[trigger()]].
+ * Event handlers may use this property to check what event it is handling.
+ */
+ public $name;
+ /**
+ * @var object the sender of this event. If not set, this property will be
+ * set as the object whose "trigger()" method is called.
+ * This property may also be a `null` when this event is a
+ * class-level event which is triggered in a static context.
+ */
+ public $sender;
+ /**
+ * @var boolean whether the event is handled. Defaults to false.
+ * When a handler sets this to be true, the event processing will stop and
+ * ignore the rest of the uninvoked event handlers.
+ */
+ public $handled = false;
+ /**
+ * @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
+ * Note that this varies according to which event handler is currently executing.
+ */
+ public $data;
- private static $_events = [];
+ private static $_events = [];
- /**
- * Attaches an event handler to a class-level event.
- *
- * When a class-level event is triggered, event handlers attached
- * to that class and all parent classes will be invoked.
- *
- * For example, the following code attaches an event handler to `ActiveRecord`'s
- * `afterInsert` event:
- *
- * ~~~
- * Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
- * Yii::trace(get_class($event->sender) . ' is inserted.');
- * });
- * ~~~
- *
- * The handler will be invoked for EVERY successful ActiveRecord insertion.
- *
- * For more details about how to declare an event handler, please refer to [[Component::on()]].
- *
- * @param string $class the fully qualified class name to which the event handler needs to attach.
- * @param string $name the event name.
- * @param callable $handler the event handler.
- * @param mixed $data the data to be passed to the event handler when the event is triggered.
- * When the event handler is invoked, this data can be accessed via [[Event::data]].
- * @see off()
- */
- public static function on($class, $name, $handler, $data = null)
- {
- self::$_events[$name][ltrim($class, '\\')][] = [$handler, $data];
- }
+ /**
+ * Attaches an event handler to a class-level event.
+ *
+ * When a class-level event is triggered, event handlers attached
+ * to that class and all parent classes will be invoked.
+ *
+ * For example, the following code attaches an event handler to `ActiveRecord`'s
+ * `afterInsert` event:
+ *
+ * ~~~
+ * Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
+ * Yii::trace(get_class($event->sender) . ' is inserted.');
+ * });
+ * ~~~
+ *
+ * The handler will be invoked for EVERY successful ActiveRecord insertion.
+ *
+ * For more details about how to declare an event handler, please refer to [[Component::on()]].
+ *
+ * @param string $class the fully qualified class name to which the event handler needs to attach.
+ * @param string $name the event name.
+ * @param callable $handler the event handler.
+ * @param mixed $data the data to be passed to the event handler when the event is triggered.
+ * When the event handler is invoked, this data can be accessed via [[Event::data]].
+ * @see off()
+ */
+ public static function on($class, $name, $handler, $data = null)
+ {
+ self::$_events[$name][ltrim($class, '\\')][] = [$handler, $data];
+ }
- /**
- * Detaches an event handler from a class-level event.
- *
- * This method is the opposite of [[on()]].
- *
- * @param string $class the fully qualified class name from which the event handler needs to be detached.
- * @param string $name the event name.
- * @param callable $handler the event handler to be removed.
- * If it is null, all handlers attached to the named event will be removed.
- * @return boolean whether a handler is found and detached.
- * @see on()
- */
- public static function off($class, $name, $handler = null)
- {
- $class = ltrim($class, '\\');
- if (empty(self::$_events[$name][$class])) {
- return false;
- }
- if ($handler === null) {
- unset(self::$_events[$name][$class]);
- return true;
- } else {
- $removed = false;
- foreach (self::$_events[$name][$class] as $i => $event) {
- if ($event[0] === $handler) {
- unset(self::$_events[$name][$class][$i]);
- $removed = true;
- }
- }
- if ($removed) {
- self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
- }
- return $removed;
- }
- }
+ /**
+ * Detaches an event handler from a class-level event.
+ *
+ * This method is the opposite of [[on()]].
+ *
+ * @param string $class the fully qualified class name from which the event handler needs to be detached.
+ * @param string $name the event name.
+ * @param callable $handler the event handler to be removed.
+ * If it is null, all handlers attached to the named event will be removed.
+ * @return boolean whether a handler is found and detached.
+ * @see on()
+ */
+ public static function off($class, $name, $handler = null)
+ {
+ $class = ltrim($class, '\\');
+ if (empty(self::$_events[$name][$class])) {
+ return false;
+ }
+ if ($handler === null) {
+ unset(self::$_events[$name][$class]);
- /**
- * Returns a value indicating whether there is any handler attached to the specified class-level event.
- * Note that this method will also check all parent classes to see if there is any handler attached
- * to the named event.
- * @param string|object $class the object or the fully qualified class name specifying the class-level event.
- * @param string $name the event name.
- * @return boolean whether there is any handler attached to the event.
- */
- public static function hasHandlers($class, $name)
- {
- if (empty(self::$_events[$name])) {
- return false;
- }
- if (is_object($class)) {
- $class = get_class($class);
- } else {
- $class = ltrim($class, '\\');
- }
- do {
- if (!empty(self::$_events[$name][$class])) {
- return true;
- }
- } while (($class = get_parent_class($class)) !== false);
- return false;
- }
+ return true;
+ } else {
+ $removed = false;
+ foreach (self::$_events[$name][$class] as $i => $event) {
+ if ($event[0] === $handler) {
+ unset(self::$_events[$name][$class][$i]);
+ $removed = true;
+ }
+ }
+ if ($removed) {
+ self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
+ }
- /**
- * Triggers a class-level event.
- * This method will cause invocation of event handlers that are attached to the named event
- * for the specified class and all its parent classes.
- * @param string|object $class the object or the fully qualified class name specifying the class-level event.
- * @param string $name the event name.
- * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
- */
- public static function trigger($class, $name, $event = null)
- {
- if (empty(self::$_events[$name])) {
- return;
- }
- if ($event === null) {
- $event = new static;
- }
- $event->handled = false;
- $event->name = $name;
+ return $removed;
+ }
+ }
- if (is_object($class)) {
- if ($event->sender === null) {
- $event->sender = $class;
- }
- $class = get_class($class);
- } else {
- $class = ltrim($class, '\\');
- }
- do {
- if (!empty(self::$_events[$name][$class])) {
- foreach (self::$_events[$name][$class] as $handler) {
- $event->data = $handler[1];
- call_user_func($handler[0], $event);
- if ($event->handled) {
- return;
- }
- }
- }
- } while (($class = get_parent_class($class)) !== false);
- }
+ /**
+ * Returns a value indicating whether there is any handler attached to the specified class-level event.
+ * Note that this method will also check all parent classes to see if there is any handler attached
+ * to the named event.
+ * @param string|object $class the object or the fully qualified class name specifying the class-level event.
+ * @param string $name the event name.
+ * @return boolean whether there is any handler attached to the event.
+ */
+ public static function hasHandlers($class, $name)
+ {
+ if (empty(self::$_events[$name])) {
+ return false;
+ }
+ if (is_object($class)) {
+ $class = get_class($class);
+ } else {
+ $class = ltrim($class, '\\');
+ }
+ do {
+ if (!empty(self::$_events[$name][$class])) {
+ return true;
+ }
+ } while (($class = get_parent_class($class)) !== false);
+
+ return false;
+ }
+
+ /**
+ * Triggers a class-level event.
+ * This method will cause invocation of event handlers that are attached to the named event
+ * for the specified class and all its parent classes.
+ * @param string|object $class the object or the fully qualified class name specifying the class-level event.
+ * @param string $name the event name.
+ * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
+ */
+ public static function trigger($class, $name, $event = null)
+ {
+ if (empty(self::$_events[$name])) {
+ return;
+ }
+ if ($event === null) {
+ $event = new static;
+ }
+ $event->handled = false;
+ $event->name = $name;
+
+ if (is_object($class)) {
+ if ($event->sender === null) {
+ $event->sender = $class;
+ }
+ $class = get_class($class);
+ } else {
+ $class = ltrim($class, '\\');
+ }
+ do {
+ if (!empty(self::$_events[$name][$class])) {
+ foreach (self::$_events[$name][$class] as $handler) {
+ $event->data = $handler[1];
+ call_user_func($handler[0], $event);
+ if ($event->handled) {
+ return;
+ }
+ }
+ }
+ } while (($class = get_parent_class($class)) !== false);
+ }
}
diff --git a/framework/base/Exception.php b/framework/base/Exception.php
index 77526f37f26..e12cf1b9f06 100644
--- a/framework/base/Exception.php
+++ b/framework/base/Exception.php
@@ -15,11 +15,11 @@
*/
class Exception extends \Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Exception';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Exception';
+ }
}
diff --git a/framework/base/Extension.php b/framework/base/Extension.php
index c25a0430b8e..0cbc0cc7be2 100644
--- a/framework/base/Extension.php
+++ b/framework/base/Extension.php
@@ -19,11 +19,11 @@
*/
class Extension
{
- /**
- * Initializes the extension.
- * This method is invoked at the end of [[Application::init()]].
- */
- public static function init()
- {
- }
+ /**
+ * Initializes the extension.
+ * This method is invoked at the end of [[Application::init()]].
+ */
+ public static function init()
+ {
+ }
}
diff --git a/framework/base/Formatter.php b/framework/base/Formatter.php
index 8888d761c7a..0e1dd6b3c3c 100644
--- a/framework/base/Formatter.php
+++ b/framework/base/Formatter.php
@@ -27,432 +27,447 @@
*/
class Formatter extends Component
{
- /**
- * @var string the timezone to use for formatting time and date values.
- * This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php)
- * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
- * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
- * If this property is not set, [[\yii\base\Application::timezone]] will be used.
- */
- public $timeZone;
- /**
- * @var string the default format string to be used to format a date using PHP date() function.
- */
- public $dateFormat = 'Y/m/d';
- /**
- * @var string the default format string to be used to format a time using PHP date() function.
- */
- public $timeFormat = 'h:i:s A';
- /**
- * @var string the default format string to be used to format a date and time using PHP date() function.
- */
- public $datetimeFormat = 'Y/m/d h:i:s A';
- /**
- * @var string the text to be displayed when formatting a null. Defaults to '(not set)'.
- */
- public $nullDisplay;
- /**
- * @var array the text to be displayed when formatting a boolean value. The first element corresponds
- * to the text display for false, the second element for true. Defaults to `['No', 'Yes']`.
- */
- public $booleanFormat;
- /**
- * @var string the character displayed as the decimal point when formatting a number.
- * If not set, "." will be used.
- */
- public $decimalSeparator;
- /**
- * @var string the character displayed as the thousands separator character when formatting a number.
- * If not set, "," will be used.
- */
- public $thousandSeparator;
- /**
- * @var array the format used to format size (bytes). Three elements may be specified: "base", "decimals" and "decimalSeparator".
- * They correspond to the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte, defaults to 1024),
- * the number of digits after the decimal point (defaults to 2) and the character displayed as the decimal point.
- */
- public $sizeFormat = [
- 'base' => 1024,
- 'decimals' => 2,
- 'decimalSeparator' => null,
- ];
-
- /**
- * Initializes the component.
- */
- public function init()
- {
- if ($this->timeZone === null) {
- $this->timeZone = Yii::$app->timeZone;
- }
-
- if (empty($this->booleanFormat)) {
- $this->booleanFormat = [Yii::t('yii', 'No'), Yii::t('yii', 'Yes')];
- }
- if ($this->nullDisplay === null) {
- $this->nullDisplay = '' . Yii::t('yii', '(not set)') . '';
- }
- }
-
- /**
- * Formats the value based on the given format type.
- * This method will call one of the "as" methods available in this class to do the formatting.
- * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
- * then [[asHtml()]] will be used. Format names are case insensitive.
- * @param mixed $value the value to be formatted
- * @param string|array $format the format of the value, e.g., "html", "text". To specify additional
- * parameters of the formatting method, you may use an array. The first element of the array
- * specifies the format name, while the rest of the elements will be used as the parameters to the formatting
- * method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`.
- * @return string the formatting result
- * @throws InvalidParamException if the type is not supported by this class.
- */
- public function format($value, $format)
- {
- if (is_array($format)) {
- if (!isset($format[0])) {
- throw new InvalidParamException('The $format array must contain at least one element.');
- }
- $f = $format[0];
- $format[0] = $value;
- $params = $format;
- $format = $f;
- } else {
- $params = [$value];
- }
- $method = 'as' . $format;
- if ($this->hasMethod($method)) {
- return call_user_func_array([$this, $method], $params);
- } else {
- throw new InvalidParamException("Unknown type: $format");
- }
- }
-
- /**
- * Formats the value as is without any formatting.
- * This method simply returns back the parameter without any format.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- */
- public function asRaw($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return $value;
- }
-
- /**
- * Formats the value as an HTML-encoded plain text.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- */
- public function asText($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return Html::encode($value);
- }
-
- /**
- * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- */
- public function asNtext($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return nl2br(Html::encode($value));
- }
-
- /**
- * Formats the value as HTML-encoded text paragraphs.
- * Each text paragraph is enclosed within a `
` tag.
- * One or multiple consecutive empty lines divide two paragraphs.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- */
- public function asParagraphs($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return str_replace('
', '',
- '
' . preg_replace('/[\r\n]{2,}/', "
\n
", Html::encode($value)) . '
'
- );
- }
-
- /**
- * Formats the value as HTML text.
- * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
- * Use [[asRaw()]] if you do not want any purification of the value.
- * @param mixed $value the value to be formatted
- * @param array|null $config the configuration for the HTMLPurifier class.
- * @return string the formatted result
- */
- public function asHtml($value, $config = null)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return HtmlPurifier::process($value, $config);
- }
-
- /**
- * Formats the value as a mailto link.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- */
- public function asEmail($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return Html::mailto(Html::encode($value), $value);
- }
-
- /**
- * Formats the value as an image tag.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- */
- public function asImage($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return Html::img($value);
- }
-
- /**
- * Formats the value as a hyperlink.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- */
- public function asUrl($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- $url = $value;
- if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
- $url = 'http://' . $url;
- }
- return Html::a(Html::encode($value), $url);
- }
-
- /**
- * Formats the value as a boolean.
- * @param mixed $value the value to be formatted
- * @return string the formatted result
- * @see booleanFormat
- */
- public function asBoolean($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
- }
-
- /**
- * Formats the value as a date.
- * @param integer|string|DateTime $value the value to be formatted. The following
- * types of value are supported:
- *
- * - an integer representing a UNIX timestamp
- * - a string that can be parsed into a UNIX timestamp via `strtotime()`
- * - a PHP DateTime object
- *
- * @param string $format the format used to convert the value into a date string.
- * If null, [[dateFormat]] will be used. The format string should be one
- * that can be recognized by the PHP `date()` function.
- * @return string the formatted result
- * @see dateFormat
- */
- public function asDate($value, $format = null)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- $value = $this->normalizeDatetimeValue($value);
- return $this->formatTimestamp($value, $format === null ? $this->dateFormat : $format);
- }
-
- /**
- * Formats the value as a time.
- * @param integer|string|DateTime $value the value to be formatted. The following
- * types of value are supported:
- *
- * - an integer representing a UNIX timestamp
- * - a string that can be parsed into a UNIX timestamp via `strtotime()`
- * - a PHP DateTime object
- *
- * @param string $format the format used to convert the value into a date string.
- * If null, [[timeFormat]] will be used. The format string should be one
- * that can be recognized by the PHP `date()` function.
- * @return string the formatted result
- * @see timeFormat
- */
- public function asTime($value, $format = null)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- $value = $this->normalizeDatetimeValue($value);
- return $this->formatTimestamp($value, $format === null ? $this->timeFormat : $format);
- }
-
- /**
- * Formats the value as a datetime.
- * @param integer|string|DateTime $value the value to be formatted. The following
- * types of value are supported:
- *
- * - an integer representing a UNIX timestamp
- * - a string that can be parsed into a UNIX timestamp via `strtotime()`
- * - a PHP DateTime object
- *
- * @param string $format the format used to convert the value into a date string.
- * If null, [[datetimeFormat]] will be used. The format string should be one
- * that can be recognized by the PHP `date()` function.
- * @return string the formatted result
- * @see datetimeFormat
- */
- public function asDatetime($value, $format = null)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- $value = $this->normalizeDatetimeValue($value);
- return $this->formatTimestamp($value, $format === null ? $this->datetimeFormat : $format);
- }
-
- /**
- * Normalizes the given datetime value as one that can be taken by various date/time formatting methods.
- * @param mixed $value the datetime value to be normalized.
- * @return integer the normalized datetime value
- */
- protected function normalizeDatetimeValue($value)
- {
- if (is_string($value)) {
- return is_numeric($value) || $value === '' ? (int)$value : strtotime($value);
- } elseif ($value instanceof DateTime || $value instanceof \DateTimeInterface) {
- return $value->getTimestamp();
- } else {
- return (int)$value;
- }
- }
-
- /**
- * @param integer $value normalized datetime value
- * @param string $format the format used to convert the value into a date string.
- * @return string the formatted result
- */
- protected function formatTimestamp($value, $format)
- {
- $date = new DateTime(null, new \DateTimeZone($this->timeZone));
- $date->setTimestamp($value);
- return $date->format($format);
- }
-
- /**
- * Formats the value as an integer.
- * @param mixed $value the value to be formatted
- * @return string the formatting result.
- */
- public function asInteger($value)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
- return $matches[1];
- } else {
- $value = (int)$value;
- return "$value";
- }
- }
-
- /**
- * Formats the value as a double number.
- * Property [[decimalSeparator]] will be used to represent the decimal point.
- * @param mixed $value the value to be formatted
- * @param integer $decimals the number of digits after the decimal point
- * @return string the formatting result.
- * @see decimalSeparator
- */
- public function asDouble($value, $decimals = 2)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- if ($this->decimalSeparator === null) {
- return sprintf("%.{$decimals}f", $value);
- } else {
- return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
- }
- }
-
- /**
- * Formats the value as a number with decimal and thousand separators.
- * This method calls the PHP number_format() function to do the formatting.
- * @param mixed $value the value to be formatted
- * @param integer $decimals the number of digits after the decimal point
- * @return string the formatted result
- * @see decimalSeparator
- * @see thousandSeparator
- */
- public function asNumber($value, $decimals = 0)
- {
- if ($value === null) {
- return $this->nullDisplay;
- }
- $ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.';
- $ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ',';
- return number_format($value, $decimals, $ds, $ts);
- }
-
- /**
- * Formats the value in bytes as a size in human readable form.
- * @param integer $value value in bytes to be formatted
- * @param boolean $verbose if full names should be used (e.g. bytes, kilobytes, ...).
- * Defaults to false meaning that short names will be used (e.g. B, KB, ...).
- * @return string the formatted result
- * @see sizeFormat
- */
- public function asSize($value, $verbose = false)
- {
- $position = 0;
-
- do {
- if ($value < $this->sizeFormat['base']) {
- break;
- }
-
- $value = $value / $this->sizeFormat['base'];
- $position++;
- } while ($position < 6);
-
- $value = round($value, $this->sizeFormat['decimals']);
- $formattedValue = isset($this->sizeFormat['decimalSeparator']) ? str_replace('.', $this->sizeFormat['decimalSeparator'], $value) : $value;
- $params = ['n' => $formattedValue];
-
- switch($position) {
- case 0:
- return $verbose ? Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params) : Yii::t('yii', '{n} B', $params);
- case 1:
- return $verbose ? Yii::t('yii', '{n, plural, =1{# kilobyte} other{# kilobytes}}', $params) : Yii::t('yii', '{n} KB', $params);
- case 2:
- return $verbose ? Yii::t('yii', '{n, plural, =1{# megabyte} other{# megabytes}}', $params) : Yii::t('yii', '{n} MB', $params);
- case 3:
- return $verbose ? Yii::t('yii', '{n, plural, =1{# gigabyte} other{# gigabytes}}', $params) : Yii::t('yii', '{n} GB', $params);
- case 4:
- return $verbose ? Yii::t('yii', '{n, plural, =1{# terabyte} other{# terabytes}}', $params) : Yii::t('yii', '{n} TB', $params);
- default:
- return $verbose ? Yii::t('yii', '{n, plural, =1{# petabyte} other{# petabytes}}', $params) : Yii::t('yii', '{n} PB', $params);
- }
- }
+ /**
+ * @var string the timezone to use for formatting time and date values.
+ * This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php)
+ * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
+ * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
+ * If this property is not set, [[\yii\base\Application::timezone]] will be used.
+ */
+ public $timeZone;
+ /**
+ * @var string the default format string to be used to format a date using PHP date() function.
+ */
+ public $dateFormat = 'Y/m/d';
+ /**
+ * @var string the default format string to be used to format a time using PHP date() function.
+ */
+ public $timeFormat = 'h:i:s A';
+ /**
+ * @var string the default format string to be used to format a date and time using PHP date() function.
+ */
+ public $datetimeFormat = 'Y/m/d h:i:s A';
+ /**
+ * @var string the text to be displayed when formatting a null. Defaults to '(not set)'.
+ */
+ public $nullDisplay;
+ /**
+ * @var array the text to be displayed when formatting a boolean value. The first element corresponds
+ * to the text display for false, the second element for true. Defaults to `['No', 'Yes']`.
+ */
+ public $booleanFormat;
+ /**
+ * @var string the character displayed as the decimal point when formatting a number.
+ * If not set, "." will be used.
+ */
+ public $decimalSeparator;
+ /**
+ * @var string the character displayed as the thousands separator character when formatting a number.
+ * If not set, "," will be used.
+ */
+ public $thousandSeparator;
+ /**
+ * @var array the format used to format size (bytes). Three elements may be specified: "base", "decimals" and "decimalSeparator".
+ * They correspond to the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte, defaults to 1024),
+ * the number of digits after the decimal point (defaults to 2) and the character displayed as the decimal point.
+ */
+ public $sizeFormat = [
+ 'base' => 1024,
+ 'decimals' => 2,
+ 'decimalSeparator' => null,
+ ];
+
+ /**
+ * Initializes the component.
+ */
+ public function init()
+ {
+ if ($this->timeZone === null) {
+ $this->timeZone = Yii::$app->timeZone;
+ }
+
+ if (empty($this->booleanFormat)) {
+ $this->booleanFormat = [Yii::t('yii', 'No'), Yii::t('yii', 'Yes')];
+ }
+ if ($this->nullDisplay === null) {
+ $this->nullDisplay = '' . Yii::t('yii', '(not set)') . '';
+ }
+ }
+
+ /**
+ * Formats the value based on the given format type.
+ * This method will call one of the "as" methods available in this class to do the formatting.
+ * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
+ * then [[asHtml()]] will be used. Format names are case insensitive.
+ * @param mixed $value the value to be formatted
+ * @param string|array $format the format of the value, e.g., "html", "text". To specify additional
+ * parameters of the formatting method, you may use an array. The first element of the array
+ * specifies the format name, while the rest of the elements will be used as the parameters to the formatting
+ * method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`.
+ * @return string the formatting result
+ * @throws InvalidParamException if the type is not supported by this class.
+ */
+ public function format($value, $format)
+ {
+ if (is_array($format)) {
+ if (!isset($format[0])) {
+ throw new InvalidParamException('The $format array must contain at least one element.');
+ }
+ $f = $format[0];
+ $format[0] = $value;
+ $params = $format;
+ $format = $f;
+ } else {
+ $params = [$value];
+ }
+ $method = 'as' . $format;
+ if ($this->hasMethod($method)) {
+ return call_user_func_array([$this, $method], $params);
+ } else {
+ throw new InvalidParamException("Unknown type: $format");
+ }
+ }
+
+ /**
+ * Formats the value as is without any formatting.
+ * This method simply returns back the parameter without any format.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asRaw($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Formats the value as an HTML-encoded plain text.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asText($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return Html::encode($value);
+ }
+
+ /**
+ * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asNtext($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return nl2br(Html::encode($value));
+ }
+
+ /**
+ * Formats the value as HTML-encoded text paragraphs.
+ * Each text paragraph is enclosed within a `
` tag.
+ * One or multiple consecutive empty lines divide two paragraphs.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asParagraphs($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return str_replace('
', '',
+ '
' . preg_replace('/[\r\n]{2,}/', "
\n
", Html::encode($value)) . '
'
+ );
+ }
+
+ /**
+ * Formats the value as HTML text.
+ * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
+ * Use [[asRaw()]] if you do not want any purification of the value.
+ * @param mixed $value the value to be formatted
+ * @param array|null $config the configuration for the HTMLPurifier class.
+ * @return string the formatted result
+ */
+ public function asHtml($value, $config = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return HtmlPurifier::process($value, $config);
+ }
+
+ /**
+ * Formats the value as a mailto link.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asEmail($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return Html::mailto(Html::encode($value), $value);
+ }
+
+ /**
+ * Formats the value as an image tag.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asImage($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return Html::img($value);
+ }
+
+ /**
+ * Formats the value as a hyperlink.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asUrl($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $url = $value;
+ if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
+ $url = 'http://' . $url;
+ }
+
+ return Html::a(Html::encode($value), $url);
+ }
+
+ /**
+ * Formats the value as a boolean.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ * @see booleanFormat
+ */
+ public function asBoolean($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+
+ return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
+ }
+
+ /**
+ * Formats the value as a date.
+ * @param integer|string|DateTime $value the value to be formatted. The following
+ * types of value are supported:
+ *
+ * - an integer representing a UNIX timestamp
+ * - a string that can be parsed into a UNIX timestamp via `strtotime()`
+ * - a PHP DateTime object
+ *
+ * @param string $format the format used to convert the value into a date string.
+ * If null, [[dateFormat]] will be used. The format string should be one
+ * that can be recognized by the PHP `date()` function.
+ * @return string the formatted result
+ * @see dateFormat
+ */
+ public function asDate($value, $format = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $value = $this->normalizeDatetimeValue($value);
+
+ return $this->formatTimestamp($value, $format === null ? $this->dateFormat : $format);
+ }
+
+ /**
+ * Formats the value as a time.
+ * @param integer|string|DateTime $value the value to be formatted. The following
+ * types of value are supported:
+ *
+ * - an integer representing a UNIX timestamp
+ * - a string that can be parsed into a UNIX timestamp via `strtotime()`
+ * - a PHP DateTime object
+ *
+ * @param string $format the format used to convert the value into a date string.
+ * If null, [[timeFormat]] will be used. The format string should be one
+ * that can be recognized by the PHP `date()` function.
+ * @return string the formatted result
+ * @see timeFormat
+ */
+ public function asTime($value, $format = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $value = $this->normalizeDatetimeValue($value);
+
+ return $this->formatTimestamp($value, $format === null ? $this->timeFormat : $format);
+ }
+
+ /**
+ * Formats the value as a datetime.
+ * @param integer|string|DateTime $value the value to be formatted. The following
+ * types of value are supported:
+ *
+ * - an integer representing a UNIX timestamp
+ * - a string that can be parsed into a UNIX timestamp via `strtotime()`
+ * - a PHP DateTime object
+ *
+ * @param string $format the format used to convert the value into a date string.
+ * If null, [[datetimeFormat]] will be used. The format string should be one
+ * that can be recognized by the PHP `date()` function.
+ * @return string the formatted result
+ * @see datetimeFormat
+ */
+ public function asDatetime($value, $format = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $value = $this->normalizeDatetimeValue($value);
+
+ return $this->formatTimestamp($value, $format === null ? $this->datetimeFormat : $format);
+ }
+
+ /**
+ * Normalizes the given datetime value as one that can be taken by various date/time formatting methods.
+ * @param mixed $value the datetime value to be normalized.
+ * @return integer the normalized datetime value
+ */
+ protected function normalizeDatetimeValue($value)
+ {
+ if (is_string($value)) {
+ return is_numeric($value) || $value === '' ? (int) $value : strtotime($value);
+ } elseif ($value instanceof DateTime || $value instanceof \DateTimeInterface) {
+ return $value->getTimestamp();
+ } else {
+ return (int) $value;
+ }
+ }
+
+ /**
+ * @param integer $value normalized datetime value
+ * @param string $format the format used to convert the value into a date string.
+ * @return string the formatted result
+ */
+ protected function formatTimestamp($value, $format)
+ {
+ $date = new DateTime(null, new \DateTimeZone($this->timeZone));
+ $date->setTimestamp($value);
+
+ return $date->format($format);
+ }
+
+ /**
+ * Formats the value as an integer.
+ * @param mixed $value the value to be formatted
+ * @return string the formatting result.
+ */
+ public function asInteger($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
+ return $matches[1];
+ } else {
+ $value = (int) $value;
+
+ return "$value";
+ }
+ }
+
+ /**
+ * Formats the value as a double number.
+ * Property [[decimalSeparator]] will be used to represent the decimal point.
+ * @param mixed $value the value to be formatted
+ * @param integer $decimals the number of digits after the decimal point
+ * @return string the formatting result.
+ * @see decimalSeparator
+ */
+ public function asDouble($value, $decimals = 2)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ if ($this->decimalSeparator === null) {
+ return sprintf("%.{$decimals}f", $value);
+ } else {
+ return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
+ }
+ }
+
+ /**
+ * Formats the value as a number with decimal and thousand separators.
+ * This method calls the PHP number_format() function to do the formatting.
+ * @param mixed $value the value to be formatted
+ * @param integer $decimals the number of digits after the decimal point
+ * @return string the formatted result
+ * @see decimalSeparator
+ * @see thousandSeparator
+ */
+ public function asNumber($value, $decimals = 0)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.';
+ $ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ',';
+
+ return number_format($value, $decimals, $ds, $ts);
+ }
+
+ /**
+ * Formats the value in bytes as a size in human readable form.
+ * @param integer $value value in bytes to be formatted
+ * @param boolean $verbose if full names should be used (e.g. bytes, kilobytes, ...).
+ * Defaults to false meaning that short names will be used (e.g. B, KB, ...).
+ * @return string the formatted result
+ * @see sizeFormat
+ */
+ public function asSize($value, $verbose = false)
+ {
+ $position = 0;
+
+ do {
+ if ($value < $this->sizeFormat['base']) {
+ break;
+ }
+
+ $value = $value / $this->sizeFormat['base'];
+ $position++;
+ } while ($position < 6);
+
+ $value = round($value, $this->sizeFormat['decimals']);
+ $formattedValue = isset($this->sizeFormat['decimalSeparator']) ? str_replace('.', $this->sizeFormat['decimalSeparator'], $value) : $value;
+ $params = ['n' => $formattedValue];
+
+ switch ($position) {
+ case 0:
+ return $verbose ? Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params) : Yii::t('yii', '{n} B', $params);
+ case 1:
+ return $verbose ? Yii::t('yii', '{n, plural, =1{# kilobyte} other{# kilobytes}}', $params) : Yii::t('yii', '{n} KB', $params);
+ case 2:
+ return $verbose ? Yii::t('yii', '{n, plural, =1{# megabyte} other{# megabytes}}', $params) : Yii::t('yii', '{n} MB', $params);
+ case 3:
+ return $verbose ? Yii::t('yii', '{n, plural, =1{# gigabyte} other{# gigabytes}}', $params) : Yii::t('yii', '{n} GB', $params);
+ case 4:
+ return $verbose ? Yii::t('yii', '{n, plural, =1{# terabyte} other{# terabytes}}', $params) : Yii::t('yii', '{n} TB', $params);
+ default:
+ return $verbose ? Yii::t('yii', '{n, plural, =1{# petabyte} other{# petabytes}}', $params) : Yii::t('yii', '{n} PB', $params);
+ }
+ }
}
diff --git a/framework/base/InlineAction.php b/framework/base/InlineAction.php
index 412b357a584..a3ef32af604 100644
--- a/framework/base/InlineAction.php
+++ b/framework/base/InlineAction.php
@@ -20,36 +20,37 @@
*/
class InlineAction extends Action
{
- /**
- * @var string the controller method that this inline action is associated with
- */
- public $actionMethod;
+ /**
+ * @var string the controller method that this inline action is associated with
+ */
+ public $actionMethod;
- /**
- * @param string $id the ID of this action
- * @param Controller $controller the controller that owns this action
- * @param string $actionMethod the controller method that this inline action is associated with
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($id, $controller, $actionMethod, $config = [])
- {
- $this->actionMethod = $actionMethod;
- parent::__construct($id, $controller, $config);
- }
+ /**
+ * @param string $id the ID of this action
+ * @param Controller $controller the controller that owns this action
+ * @param string $actionMethod the controller method that this inline action is associated with
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $controller, $actionMethod, $config = [])
+ {
+ $this->actionMethod = $actionMethod;
+ parent::__construct($id, $controller, $config);
+ }
- /**
- * Runs this action with the specified parameters.
- * This method is mainly invoked by the controller.
- * @param array $params action parameters
- * @return mixed the result of the action
- */
- public function runWithParams($params)
- {
- $args = $this->controller->bindActionParams($this, $params);
- Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
- if (Yii::$app->requestedParams === null) {
- Yii::$app->requestedParams = $args;
- }
- return call_user_func_array([$this->controller, $this->actionMethod], $args);
- }
+ /**
+ * Runs this action with the specified parameters.
+ * This method is mainly invoked by the controller.
+ * @param array $params action parameters
+ * @return mixed the result of the action
+ */
+ public function runWithParams($params)
+ {
+ $args = $this->controller->bindActionParams($this, $params);
+ Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
+ if (Yii::$app->requestedParams === null) {
+ Yii::$app->requestedParams = $args;
+ }
+
+ return call_user_func_array([$this->controller, $this->actionMethod], $args);
+ }
}
diff --git a/framework/base/InvalidCallException.php b/framework/base/InvalidCallException.php
index bc46ca46fa3..fad85e1976a 100644
--- a/framework/base/InvalidCallException.php
+++ b/framework/base/InvalidCallException.php
@@ -15,11 +15,11 @@
*/
class InvalidCallException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Invalid Call';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Invalid Call';
+ }
}
diff --git a/framework/base/InvalidConfigException.php b/framework/base/InvalidConfigException.php
index 1fb0b4aef4e..953675f9303 100644
--- a/framework/base/InvalidConfigException.php
+++ b/framework/base/InvalidConfigException.php
@@ -15,11 +15,11 @@
*/
class InvalidConfigException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Invalid Configuration';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Invalid Configuration';
+ }
}
diff --git a/framework/base/InvalidParamException.php b/framework/base/InvalidParamException.php
index b2f919185ab..62c5f815caf 100644
--- a/framework/base/InvalidParamException.php
+++ b/framework/base/InvalidParamException.php
@@ -15,11 +15,11 @@
*/
class InvalidParamException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Invalid Parameter';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Invalid Parameter';
+ }
}
diff --git a/framework/base/InvalidRouteException.php b/framework/base/InvalidRouteException.php
index b94fab684c2..43bed6ef923 100644
--- a/framework/base/InvalidRouteException.php
+++ b/framework/base/InvalidRouteException.php
@@ -15,11 +15,11 @@
*/
class InvalidRouteException extends UserException
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Invalid Route';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Invalid Route';
+ }
}
diff --git a/framework/base/MailEvent.php b/framework/base/MailEvent.php
index 758bd9044f1..762b9597577 100644
--- a/framework/base/MailEvent.php
+++ b/framework/base/MailEvent.php
@@ -18,18 +18,18 @@
class MailEvent extends Event
{
- /**
- * @var \yii\mail\MessageInterface mail message being send
- */
- public $message;
- /**
- * @var boolean if message send was successful
- */
- public $isSuccessful;
- /**
- * @var boolean whether to continue sending an email. Event handlers of
- * [[\yii\mail\BaseMailer::EVENT_BEFORE_SEND]] may set this property to decide whether
- * to continue send or not.
- */
- public $isValid = true;
+ /**
+ * @var \yii\mail\MessageInterface mail message being send
+ */
+ public $message;
+ /**
+ * @var boolean if message send was successful
+ */
+ public $isSuccessful;
+ /**
+ * @var boolean whether to continue sending an email. Event handlers of
+ * [[\yii\mail\BaseMailer::EVENT_BEFORE_SEND]] may set this property to decide whether
+ * to continue send or not.
+ */
+ public $isValid = true;
}
diff --git a/framework/base/Model.php b/framework/base/Model.php
index 681db484196..8a4e93df114 100644
--- a/framework/base/Model.php
+++ b/framework/base/Model.php
@@ -55,886 +55,905 @@
*/
class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable
{
- use ArrayableTrait;
-
- /**
- * The name of the default scenario.
- */
- const SCENARIO_DEFAULT = 'default';
- /**
- * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
- * [[ModelEvent::isValid]] to be false to stop the validation.
- */
- const EVENT_BEFORE_VALIDATE = 'beforeValidate';
- /**
- * @event Event an event raised at the end of [[validate()]]
- */
- const EVENT_AFTER_VALIDATE = 'afterValidate';
-
- /**
- * @var array validation errors (attribute name => array of errors)
- */
- private $_errors;
- /**
- * @var ArrayObject list of validators
- */
- private $_validators;
- /**
- * @var string current scenario
- */
- private $_scenario = self::SCENARIO_DEFAULT;
-
- /**
- * Returns the validation rules for attributes.
- *
- * Validation rules are used by [[validate()]] to check if attribute values are valid.
- * Child classes may override this method to declare different validation rules.
- *
- * Each rule is an array with the following structure:
- *
- * ~~~
- * [
- * ['attribute1', 'attribute2'],
- * 'validator type',
- * 'on' => ['scenario1', 'scenario2'],
- * ...other parameters...
- * ]
- * ~~~
- *
- * where
- *
- * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string;
- * - validator type: required, specifies the validator to be used. It can be a built-in validator name,
- * a method name of the model class, an anonymous function, or a validator class name.
- * - on: optional, specifies the [[scenario|scenarios]] array when the validation
- * rule can be applied. If this option is not set, the rule will apply to all scenarios.
- * - additional name-value pairs can be specified to initialize the corresponding validator properties.
- * Please refer to individual validator class API for possible properties.
- *
- * A validator can be either an object of a class extending [[Validator]], or a model class method
- * (called *inline validator*) that has the following signature:
- *
- * ~~~
- * // $params refers to validation parameters given in the rule
- * function validatorName($attribute, $params)
- * ~~~
- *
- * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of
- * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value
- * can be accessed as `$this->[$attribute]`.
- *
- * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
- * They each has an alias name which can be used when specifying a validation rule.
- *
- * Below are some examples:
- *
- * ~~~
- * [
- * // built-in "required" validator
- * [['username', 'password'], 'required'],
- * // built-in "string" validator customized with "min" and "max" properties
- * ['username', 'string', 'min' => 3, 'max' => 12],
- * // built-in "compare" validator that is used in "register" scenario only
- * ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'],
- * // an inline validator defined via the "authenticate()" method in the model class
- * ['password', 'authenticate', 'on' => 'login'],
- * // a validator of class "DateRangeValidator"
- * ['dateRange', 'DateRangeValidator'],
- * ];
- * ~~~
- *
- * Note, in order to inherit rules defined in the parent class, a child class needs to
- * merge the parent rules with child rules using functions such as `array_merge()`.
- *
- * @return array validation rules
- * @see scenarios()
- */
- public function rules()
- {
- return [];
- }
-
- /**
- * Returns a list of scenarios and the corresponding active attributes.
- * An active attribute is one that is subject to validation in the current scenario.
- * The returned array should be in the following format:
- *
- * ~~~
- * [
- * 'scenario1' => ['attribute11', 'attribute12', ...],
- * 'scenario2' => ['attribute21', 'attribute22', ...],
- * ...
- * ]
- * ~~~
- *
- * By default, an active attribute is considered safe and can be massively assigned.
- * If an attribute should NOT be massively assigned (thus considered unsafe),
- * please prefix the attribute with an exclamation character (e.g. '!rank').
- *
- * The default implementation of this method will return all scenarios found in the [[rules()]]
- * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes
- * found in the [[rules()]]. Each scenario will be associated with the attributes that
- * are being validated by the validation rules that apply to the scenario.
- *
- * @return array a list of scenarios and the corresponding active attributes.
- */
- public function scenarios()
- {
- $scenarios = [self::SCENARIO_DEFAULT => []];
- foreach ($this->getValidators() as $validator) {
- foreach ($validator->on as $scenario) {
- $scenarios[$scenario] = [];
- }
- foreach ($validator->except as $scenario) {
- $scenarios[$scenario] = [];
- }
- }
- $names = array_keys($scenarios);
-
- foreach ($this->getValidators() as $validator) {
- if (empty($validator->on) && empty($validator->except)) {
- foreach ($names as $name) {
- foreach ($validator->attributes as $attribute) {
- $scenarios[$name][$attribute] = true;
- }
- }
- } elseif (empty($validator->on)) {
- foreach ($names as $name) {
- if (!in_array($name, $validator->except, true)) {
- foreach ($validator->attributes as $attribute) {
- $scenarios[$name][$attribute] = true;
- }
- }
- }
- } else {
- foreach ($validator->on as $name) {
- foreach ($validator->attributes as $attribute) {
- $scenarios[$name][$attribute] = true;
- }
- }
- }
- }
-
- foreach ($scenarios as $scenario => $attributes) {
- if (empty($attributes) && $scenario !== self::SCENARIO_DEFAULT) {
- unset($scenarios[$scenario]);
- } else {
- $scenarios[$scenario] = array_keys($attributes);
- }
- }
-
- return $scenarios;
- }
-
- /**
- * Returns the form name that this model class should use.
- *
- * The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name
- * the input fields for the attributes in a model. If the form name is "A" and an attribute
- * name is "b", then the corresponding input name would be "A[b]". If the form name is
- * an empty string, then the input name would be "b".
- *
- * By default, this method returns the model class name (without the namespace part)
- * as the form name. You may override it when the model is used in different forms.
- *
- * @return string the form name of this model class.
- */
- public function formName()
- {
- $reflector = new ReflectionClass($this);
- return $reflector->getShortName();
- }
-
- /**
- * Returns the list of attribute names.
- * By default, this method returns all public non-static properties of the class.
- * You may override this method to change the default behavior.
- * @return array list of attribute names.
- */
- public function attributes()
- {
- $class = new ReflectionClass($this);
- $names = [];
- foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
- if (!$property->isStatic()) {
- $names[] = $property->getName();
- }
- }
- return $names;
- }
-
- /**
- * Returns the attribute labels.
- *
- * Attribute labels are mainly used for display purpose. For example, given an attribute
- * `firstName`, we can declare a label `First Name` which is more user-friendly and can
- * be displayed to end users.
- *
- * By default an attribute label is generated using [[generateAttributeLabel()]].
- * This method allows you to explicitly specify attribute labels.
- *
- * Note, in order to inherit labels defined in the parent class, a child class needs to
- * merge the parent labels with child labels using functions such as `array_merge()`.
- *
- * @return array attribute labels (name => label)
- * @see generateAttributeLabel()
- */
- public function attributeLabels()
- {
- return [];
- }
-
- /**
- * Performs the data validation.
- *
- * This method executes the validation rules applicable to the current [[scenario]].
- * The following criteria are used to determine whether a rule is currently applicable:
- *
- * - the rule must be associated with the attributes relevant to the current scenario;
- * - the rules must be effective for the current scenario.
- *
- * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
- * after the actual validation, respectively. If [[beforeValidate()]] returns false,
- * the validation will be cancelled and [[afterValidate()]] will not be called.
- *
- * Errors found during the validation can be retrieved via [[getErrors()]],
- * [[getFirstErrors()]] and [[getFirstError()]].
- *
- * @param array $attributes list of attributes that should be validated.
- * If this parameter is empty, it means any attribute listed in the applicable
- * validation rules should be validated.
- * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
- * @return boolean whether the validation is successful without any error.
- * @throws InvalidParamException if the current scenario is unknown.
- */
- public function validate($attributes = null, $clearErrors = true)
- {
- $scenarios = $this->scenarios();
- $scenario = $this->getScenario();
- if (!isset($scenarios[$scenario])) {
- throw new InvalidParamException("Unknown scenario: $scenario");
- }
-
- if ($clearErrors) {
- $this->clearErrors();
- }
- if ($attributes === null) {
- $attributes = $this->activeAttributes();
- }
- if ($this->beforeValidate()) {
- foreach ($this->getActiveValidators() as $validator) {
- $validator->validateAttributes($this, $attributes);
- }
- $this->afterValidate();
- return !$this->hasErrors();
- }
- return false;
- }
-
- /**
- * This method is invoked before validation starts.
- * The default implementation raises a `beforeValidate` event.
- * You may override this method to do preliminary checks before validation.
- * Make sure the parent implementation is invoked so that the event can be raised.
- * @return boolean whether the validation should be executed. Defaults to true.
- * If false is returned, the validation will stop and the model is considered invalid.
- */
- public function beforeValidate()
- {
- $event = new ModelEvent;
- $this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
- return $event->isValid;
- }
-
- /**
- * This method is invoked after validation ends.
- * The default implementation raises an `afterValidate` event.
- * You may override this method to do postprocessing after validation.
- * Make sure the parent implementation is invoked so that the event can be raised.
- */
- public function afterValidate()
- {
- $this->trigger(self::EVENT_AFTER_VALIDATE);
- }
-
- /**
- * Returns all the validators declared in [[rules()]].
- *
- * This method differs from [[getActiveValidators()]] in that the latter
- * only returns the validators applicable to the current [[scenario]].
- *
- * Because this method returns an ArrayObject object, you may
- * manipulate it by inserting or removing validators (useful in model behaviors).
- * For example,
- *
- * ~~~
- * $model->validators[] = $newValidator;
- * ~~~
- *
- * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model.
- */
- public function getValidators()
- {
- if ($this->_validators === null) {
- $this->_validators = $this->createValidators();
- }
- return $this->_validators;
- }
-
- /**
- * Returns the validators applicable to the current [[scenario]].
- * @param string $attribute the name of the attribute whose applicable validators should be returned.
- * If this is null, the validators for ALL attributes in the model will be returned.
- * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
- */
- public function getActiveValidators($attribute = null)
- {
- $validators = [];
- $scenario = $this->getScenario();
- foreach ($this->getValidators() as $validator) {
- if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
- $validators[] = $validator;
- }
- }
- return $validators;
- }
-
- /**
- * Creates validator objects based on the validation rules specified in [[rules()]].
- * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
- * @return ArrayObject validators
- * @throws InvalidConfigException if any validation rule configuration is invalid
- */
- public function createValidators()
- {
- $validators = new ArrayObject;
- foreach ($this->rules() as $rule) {
- if ($rule instanceof Validator) {
- $validators->append($rule);
- } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
- $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
- $validators->append($validator);
- } else {
- throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
- }
- }
- return $validators;
- }
-
- /**
- * Returns a value indicating whether the attribute is required.
- * This is determined by checking if the attribute is associated with a
- * [[\yii\validators\RequiredValidator|required]] validation rule in the
- * current [[scenario]].
- * @param string $attribute attribute name
- * @return boolean whether the attribute is required
- */
- public function isAttributeRequired($attribute)
- {
- foreach ($this->getActiveValidators($attribute) as $validator) {
- if ($validator instanceof RequiredValidator) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns a value indicating whether the attribute is safe for massive assignments.
- * @param string $attribute attribute name
- * @return boolean whether the attribute is safe for massive assignments
- * @see safeAttributes()
- */
- public function isAttributeSafe($attribute)
- {
- return in_array($attribute, $this->safeAttributes(), true);
- }
-
- /**
- * Returns a value indicating whether the attribute is active in the current scenario.
- * @param string $attribute attribute name
- * @return boolean whether the attribute is active in the current scenario
- * @see activeAttributes()
- */
- public function isAttributeActive($attribute)
- {
- return in_array($attribute, $this->activeAttributes(), true);
- }
-
- /**
- * Returns the text label for the specified attribute.
- * @param string $attribute the attribute name
- * @return string the attribute label
- * @see generateAttributeLabel()
- * @see attributeLabels()
- */
- public function getAttributeLabel($attribute)
- {
- $labels = $this->attributeLabels();
- return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
- }
-
- /**
- * Returns a value indicating whether there is any validation error.
- * @param string|null $attribute attribute name. Use null to check all attributes.
- * @return boolean whether there is any error.
- */
- public function hasErrors($attribute = null)
- {
- return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
- }
-
- /**
- * Returns the errors for all attribute or a single attribute.
- * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
- * @property array An array of errors for all attributes. Empty array is returned if no error.
- * The result is a two-dimensional array. See [[getErrors()]] for detailed description.
- * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
- * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following:
- *
- * ~~~
- * [
- * 'username' => [
- * 'Username is required.',
- * 'Username must contain only word characters.',
- * ],
- * 'email' => [
- * 'Email address is invalid.',
- * ]
- * ]
- * ~~~
- *
- * @see getFirstErrors()
- * @see getFirstError()
- */
- public function getErrors($attribute = null)
- {
- if ($attribute === null) {
- return $this->_errors === null ? [] : $this->_errors;
- } else {
- return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
- }
- }
-
- /**
- * Returns the first error of every attribute in the model.
- * @return array the first errors. The array keys are the attribute names, and the array
- * values are the corresponding error messages. An empty array will be returned if there is no error.
- * @see getErrors()
- * @see getFirstError()
- */
- public function getFirstErrors()
- {
- if (empty($this->_errors)) {
- return [];
- } else {
- $errors = [];
- foreach ($this->_errors as $name => $es) {
- if (!empty($es)) {
- $errors[$name] = reset($es);
- }
- }
- return $errors;
- }
- }
-
- /**
- * Returns the first error of the specified attribute.
- * @param string $attribute attribute name.
- * @return string the error message. Null is returned if no error.
- * @see getErrors()
- * @see getFirstErrors()
- */
- public function getFirstError($attribute)
- {
- return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
- }
-
- /**
- * Adds a new error to the specified attribute.
- * @param string $attribute attribute name
- * @param string $error new error message
- */
- public function addError($attribute, $error = '')
- {
- $this->_errors[$attribute][] = $error;
- }
-
- /**
- * Removes errors for all attributes or a single attribute.
- * @param string $attribute attribute name. Use null to remove errors for all attribute.
- */
- public function clearErrors($attribute = null)
- {
- if ($attribute === null) {
- $this->_errors = [];
- } else {
- unset($this->_errors[$attribute]);
- }
- }
-
- /**
- * Generates a user friendly attribute label based on the give attribute name.
- * This is done by replacing underscores, dashes and dots with blanks and
- * changing the first letter of each word to upper case.
- * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'.
- * @param string $name the column name
- * @return string the attribute label
- */
- public function generateAttributeLabel($name)
- {
- return Inflector::camel2words($name, true);
- }
-
- /**
- * Returns attribute values.
- * @param array $names list of attributes whose value needs to be returned.
- * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
- * If it is an array, only the attributes in the array will be returned.
- * @param array $except list of attributes whose value should NOT be returned.
- * @return array attribute values (name => value).
- */
- public function getAttributes($names = null, $except = [])
- {
- $values = [];
- if ($names === null) {
- $names = $this->attributes();
- }
- foreach ($names as $name) {
- $values[$name] = $this->$name;
- }
- foreach ($except as $name) {
- unset($values[$name]);
- }
-
- return $values;
- }
-
- /**
- * Sets the attribute values in a massive way.
- * @param array $values attribute values (name => value) to be assigned to the model.
- * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
- * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
- * @see safeAttributes()
- * @see attributes()
- */
- public function setAttributes($values, $safeOnly = true)
- {
- if (is_array($values)) {
- $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
- foreach ($values as $name => $value) {
- if (isset($attributes[$name])) {
- $this->$name = $value;
- } elseif ($safeOnly) {
- $this->onUnsafeAttribute($name, $value);
- }
- }
- }
- }
-
- /**
- * This method is invoked when an unsafe attribute is being massively assigned.
- * The default implementation will log a warning message if YII_DEBUG is on.
- * It does nothing otherwise.
- * @param string $name the unsafe attribute name
- * @param mixed $value the attribute value
- */
- public function onUnsafeAttribute($name, $value)
- {
- if (YII_DEBUG) {
- Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
- }
- }
-
- /**
- * Returns the scenario that this model is used in.
- *
- * Scenario affects how validation is performed and which attributes can
- * be massively assigned.
- *
- * @return string the scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
- */
- public function getScenario()
- {
- return $this->_scenario;
- }
-
- /**
- * Sets the scenario for the model.
- * Note that this method does not check if the scenario exists or not.
- * The method [[validate()]] will perform this check.
- * @param string $value the scenario that this model is in.
- */
- public function setScenario($value)
- {
- $this->_scenario = $value;
- }
-
- /**
- * Returns the attribute names that are safe to be massively assigned in the current scenario.
- * @return string[] safe attribute names
- */
- public function safeAttributes()
- {
- $scenario = $this->getScenario();
- $scenarios = $this->scenarios();
- if (!isset($scenarios[$scenario])) {
- return [];
- }
- $attributes = [];
- foreach ($scenarios[$scenario] as $attribute) {
- if ($attribute[0] !== '!') {
- $attributes[] = $attribute;
- }
- }
- return $attributes;
- }
-
- /**
- * Returns the attribute names that are subject to validation in the current scenario.
- * @return string[] safe attribute names
- */
- public function activeAttributes()
- {
- $scenario = $this->getScenario();
- $scenarios = $this->scenarios();
- if (!isset($scenarios[$scenario])) {
- return [];
- }
- $attributes = $scenarios[$scenario];
- foreach ($attributes as $i => $attribute) {
- if ($attribute[0] === '!') {
- $attributes[$i] = substr($attribute, 1);
- }
- }
- return $attributes;
- }
-
- /**
- * Populates the model with the data from end user.
- * The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]].
- * If [[formName()]] is empty, the whole `$data` array will be used to populate the model.
- * The data being populated is subject to the safety check by [[setAttributes()]].
- * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
- * supplied by end user.
- * @param string $formName the form name to be used for loading the data into the model.
- * If not set, [[formName()]] will be used.
- * @return boolean whether the model is successfully populated with some data.
- */
- public function load($data, $formName = null)
- {
- $scope = $formName === null ? $this->formName() : $formName;
- if ($scope == '' && !empty($data)) {
- $this->setAttributes($data);
- return true;
- } elseif (isset($data[$scope])) {
- $this->setAttributes($data[$scope]);
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Populates a set of models with the data from end user.
- * This method is mainly used to collect tabular data input.
- * The data to be loaded for each model is `$data[formName][index]`, where `formName`
- * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
- * If [[formName()]] is empty, `$data[index]` will be used to populate each model.
- * The data being populated to each model is subject to the safety check by [[setAttributes()]].
- * @param array $models the models to be populated. Note that all models should have the same class.
- * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
- * supplied by end user.
- * @return boolean whether the model is successfully populated with some data.
- */
- public static function loadMultiple($models, $data)
- {
- /** @var Model $model */
- $model = reset($models);
- if ($model === false) {
- return false;
- }
- $success = false;
- $scope = $model->formName();
- foreach ($models as $i => $model) {
- if ($scope == '') {
- if (isset($data[$i])) {
- $model->setAttributes($data[$i]);
- $success = true;
- }
- } elseif (isset($data[$scope][$i])) {
- $model->setAttributes($data[$scope][$i]);
- $success = true;
- }
- }
- return $success;
- }
-
- /**
- * Validates multiple models.
- * This method will validate every model. The models being validated may
- * be of the same or different types.
- * @param array $models the models to be validated
- * @param array $attributes list of attributes that should be validated.
- * If this parameter is empty, it means any attribute listed in the applicable
- * validation rules should be validated.
- * @return boolean whether all models are valid. False will be returned if one
- * or multiple models have validation error.
- */
- public static function validateMultiple($models, $attributes = null)
- {
- $valid = true;
- /** @var Model $model */
- foreach ($models as $model) {
- $valid = $model->validate($attributes) && $valid;
- }
- return $valid;
- }
-
- /**
- * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
- *
- * A field is a named element in the returned array by [[toArray()]].
- *
- * This method should return an array of field names or field definitions.
- * If the former, the field name will be treated as an object property name whose value will be used
- * as the field value. If the latter, the array key should be the field name while the array value should be
- * the corresponding field definition which can be either an object property name or a PHP callable
- * returning the corresponding field value. The signature of the callable should be:
- *
- * ```php
- * function ($field, $model) {
- * // return field value
- * }
- * ```
- *
- * For example, the following code declares four fields:
- *
- * - `email`: the field name is the same as the property name `email`;
- * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
- * values are obtained from the `first_name` and `last_name` properties;
- * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
- * and `last_name`.
- *
- * ```php
- * return [
- * 'email',
- * 'firstName' => 'first_name',
- * 'lastName' => 'last_name',
- * 'fullName' => function () {
- * return $this->first_name . ' ' . $this->last_name;
- * },
- * ];
- * ```
- *
- * In this method, you may also want to return different lists of fields based on some context
- * information. For example, depending on [[scenario]] or the privilege of the current application user,
- * you may return different sets of visible fields or filter out some fields.
- *
- * The default implementation of this method returns [[attributes()]] indexed by the same attribute names.
- *
- * @return array the list of field names or field definitions.
- * @see toArray()
- */
- public function fields()
- {
- $fields = $this->attributes();
- return array_combine($fields, $fields);
- }
-
- /**
- * Determines which fields can be returned by [[toArray()]].
- * This method will check the requested fields against those declared in [[fields()]] and [[extraFields()]]
- * to determine which fields can be returned.
- * @param array $fields the fields being requested for exporting
- * @param array $expand the additional fields being requested for exporting
- * @return array the list of fields to be exported. The array keys are the field names, and the array values
- * are the corresponding object property names or PHP callables returning the field values.
- */
- protected function resolveFields(array $fields, array $expand)
- {
- $result = [];
-
- foreach ($this->fields() as $field => $definition) {
- if (is_integer($field)) {
- $field = $definition;
- }
- if (empty($fields) || in_array($field, $fields, true)) {
- $result[$field] = $definition;
- }
- }
-
- if (empty($expand)) {
- return $result;
- }
-
- foreach ($this->extraFields() as $field => $definition) {
- if (is_integer($field)) {
- $field = $definition;
- }
- if (in_array($field, $expand, true)) {
- $result[$field] = $definition;
- }
- }
-
- return $result;
- }
-
- /**
- * Returns an iterator for traversing the attributes in the model.
- * This method is required by the interface IteratorAggregate.
- * @return ArrayIterator an iterator for traversing the items in the list.
- */
- public function getIterator()
- {
- $attributes = $this->getAttributes();
- return new ArrayIterator($attributes);
- }
-
- /**
- * Returns whether there is an element at the specified offset.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `isset($model[$offset])`.
- * @param mixed $offset the offset to check on
- * @return boolean
- */
- public function offsetExists($offset)
- {
- return $this->$offset !== null;
- }
-
- /**
- * Returns the element at the specified offset.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `$value = $model[$offset];`.
- * @param mixed $offset the offset to retrieve element.
- * @return mixed the element at the offset, null if no element is found at the offset
- */
- public function offsetGet($offset)
- {
- return $this->$offset;
- }
-
- /**
- * Sets the element at the specified offset.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `$model[$offset] = $item;`.
- * @param integer $offset the offset to set element
- * @param mixed $item the element value
- */
- public function offsetSet($offset, $item)
- {
- $this->$offset = $item;
- }
-
- /**
- * Sets the element value at the specified offset to null.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `unset($model[$offset])`.
- * @param mixed $offset the offset to unset element
- */
- public function offsetUnset($offset)
- {
- $this->$offset = null;
- }
+ use ArrayableTrait;
+
+ /**
+ * The name of the default scenario.
+ */
+ const SCENARIO_DEFAULT = 'default';
+ /**
+ * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
+ * [[ModelEvent::isValid]] to be false to stop the validation.
+ */
+ const EVENT_BEFORE_VALIDATE = 'beforeValidate';
+ /**
+ * @event Event an event raised at the end of [[validate()]]
+ */
+ const EVENT_AFTER_VALIDATE = 'afterValidate';
+
+ /**
+ * @var array validation errors (attribute name => array of errors)
+ */
+ private $_errors;
+ /**
+ * @var ArrayObject list of validators
+ */
+ private $_validators;
+ /**
+ * @var string current scenario
+ */
+ private $_scenario = self::SCENARIO_DEFAULT;
+
+ /**
+ * Returns the validation rules for attributes.
+ *
+ * Validation rules are used by [[validate()]] to check if attribute values are valid.
+ * Child classes may override this method to declare different validation rules.
+ *
+ * Each rule is an array with the following structure:
+ *
+ * ~~~
+ * [
+ * ['attribute1', 'attribute2'],
+ * 'validator type',
+ * 'on' => ['scenario1', 'scenario2'],
+ * ...other parameters...
+ * ]
+ * ~~~
+ *
+ * where
+ *
+ * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string;
+ * - validator type: required, specifies the validator to be used. It can be a built-in validator name,
+ * a method name of the model class, an anonymous function, or a validator class name.
+ * - on: optional, specifies the [[scenario|scenarios]] array when the validation
+ * rule can be applied. If this option is not set, the rule will apply to all scenarios.
+ * - additional name-value pairs can be specified to initialize the corresponding validator properties.
+ * Please refer to individual validator class API for possible properties.
+ *
+ * A validator can be either an object of a class extending [[Validator]], or a model class method
+ * (called *inline validator*) that has the following signature:
+ *
+ * ~~~
+ * // $params refers to validation parameters given in the rule
+ * function validatorName($attribute, $params)
+ * ~~~
+ *
+ * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of
+ * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value
+ * can be accessed as `$this->[$attribute]`.
+ *
+ * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
+ * They each has an alias name which can be used when specifying a validation rule.
+ *
+ * Below are some examples:
+ *
+ * ~~~
+ * [
+ * // built-in "required" validator
+ * [['username', 'password'], 'required'],
+ * // built-in "string" validator customized with "min" and "max" properties
+ * ['username', 'string', 'min' => 3, 'max' => 12],
+ * // built-in "compare" validator that is used in "register" scenario only
+ * ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'],
+ * // an inline validator defined via the "authenticate()" method in the model class
+ * ['password', 'authenticate', 'on' => 'login'],
+ * // a validator of class "DateRangeValidator"
+ * ['dateRange', 'DateRangeValidator'],
+ * ];
+ * ~~~
+ *
+ * Note, in order to inherit rules defined in the parent class, a child class needs to
+ * merge the parent rules with child rules using functions such as `array_merge()`.
+ *
+ * @return array validation rules
+ * @see scenarios()
+ */
+ public function rules()
+ {
+ return [];
+ }
+
+ /**
+ * Returns a list of scenarios and the corresponding active attributes.
+ * An active attribute is one that is subject to validation in the current scenario.
+ * The returned array should be in the following format:
+ *
+ * ~~~
+ * [
+ * 'scenario1' => ['attribute11', 'attribute12', ...],
+ * 'scenario2' => ['attribute21', 'attribute22', ...],
+ * ...
+ * ]
+ * ~~~
+ *
+ * By default, an active attribute is considered safe and can be massively assigned.
+ * If an attribute should NOT be massively assigned (thus considered unsafe),
+ * please prefix the attribute with an exclamation character (e.g. '!rank').
+ *
+ * The default implementation of this method will return all scenarios found in the [[rules()]]
+ * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes
+ * found in the [[rules()]]. Each scenario will be associated with the attributes that
+ * are being validated by the validation rules that apply to the scenario.
+ *
+ * @return array a list of scenarios and the corresponding active attributes.
+ */
+ public function scenarios()
+ {
+ $scenarios = [self::SCENARIO_DEFAULT => []];
+ foreach ($this->getValidators() as $validator) {
+ foreach ($validator->on as $scenario) {
+ $scenarios[$scenario] = [];
+ }
+ foreach ($validator->except as $scenario) {
+ $scenarios[$scenario] = [];
+ }
+ }
+ $names = array_keys($scenarios);
+
+ foreach ($this->getValidators() as $validator) {
+ if (empty($validator->on) && empty($validator->except)) {
+ foreach ($names as $name) {
+ foreach ($validator->attributes as $attribute) {
+ $scenarios[$name][$attribute] = true;
+ }
+ }
+ } elseif (empty($validator->on)) {
+ foreach ($names as $name) {
+ if (!in_array($name, $validator->except, true)) {
+ foreach ($validator->attributes as $attribute) {
+ $scenarios[$name][$attribute] = true;
+ }
+ }
+ }
+ } else {
+ foreach ($validator->on as $name) {
+ foreach ($validator->attributes as $attribute) {
+ $scenarios[$name][$attribute] = true;
+ }
+ }
+ }
+ }
+
+ foreach ($scenarios as $scenario => $attributes) {
+ if (empty($attributes) && $scenario !== self::SCENARIO_DEFAULT) {
+ unset($scenarios[$scenario]);
+ } else {
+ $scenarios[$scenario] = array_keys($attributes);
+ }
+ }
+
+ return $scenarios;
+ }
+
+ /**
+ * Returns the form name that this model class should use.
+ *
+ * The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name
+ * the input fields for the attributes in a model. If the form name is "A" and an attribute
+ * name is "b", then the corresponding input name would be "A[b]". If the form name is
+ * an empty string, then the input name would be "b".
+ *
+ * By default, this method returns the model class name (without the namespace part)
+ * as the form name. You may override it when the model is used in different forms.
+ *
+ * @return string the form name of this model class.
+ */
+ public function formName()
+ {
+ $reflector = new ReflectionClass($this);
+
+ return $reflector->getShortName();
+ }
+
+ /**
+ * Returns the list of attribute names.
+ * By default, this method returns all public non-static properties of the class.
+ * You may override this method to change the default behavior.
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ $class = new ReflectionClass($this);
+ $names = [];
+ foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
+ if (!$property->isStatic()) {
+ $names[] = $property->getName();
+ }
+ }
+
+ return $names;
+ }
+
+ /**
+ * Returns the attribute labels.
+ *
+ * Attribute labels are mainly used for display purpose. For example, given an attribute
+ * `firstName`, we can declare a label `First Name` which is more user-friendly and can
+ * be displayed to end users.
+ *
+ * By default an attribute label is generated using [[generateAttributeLabel()]].
+ * This method allows you to explicitly specify attribute labels.
+ *
+ * Note, in order to inherit labels defined in the parent class, a child class needs to
+ * merge the parent labels with child labels using functions such as `array_merge()`.
+ *
+ * @return array attribute labels (name => label)
+ * @see generateAttributeLabel()
+ */
+ public function attributeLabels()
+ {
+ return [];
+ }
+
+ /**
+ * Performs the data validation.
+ *
+ * This method executes the validation rules applicable to the current [[scenario]].
+ * The following criteria are used to determine whether a rule is currently applicable:
+ *
+ * - the rule must be associated with the attributes relevant to the current scenario;
+ * - the rules must be effective for the current scenario.
+ *
+ * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
+ * after the actual validation, respectively. If [[beforeValidate()]] returns false,
+ * the validation will be cancelled and [[afterValidate()]] will not be called.
+ *
+ * Errors found during the validation can be retrieved via [[getErrors()]],
+ * [[getFirstErrors()]] and [[getFirstError()]].
+ *
+ * @param array $attributes list of attributes that should be validated.
+ * If this parameter is empty, it means any attribute listed in the applicable
+ * validation rules should be validated.
+ * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
+ * @return boolean whether the validation is successful without any error.
+ * @throws InvalidParamException if the current scenario is unknown.
+ */
+ public function validate($attributes = null, $clearErrors = true)
+ {
+ $scenarios = $this->scenarios();
+ $scenario = $this->getScenario();
+ if (!isset($scenarios[$scenario])) {
+ throw new InvalidParamException("Unknown scenario: $scenario");
+ }
+
+ if ($clearErrors) {
+ $this->clearErrors();
+ }
+ if ($attributes === null) {
+ $attributes = $this->activeAttributes();
+ }
+ if ($this->beforeValidate()) {
+ foreach ($this->getActiveValidators() as $validator) {
+ $validator->validateAttributes($this, $attributes);
+ }
+ $this->afterValidate();
+
+ return !$this->hasErrors();
+ }
+
+ return false;
+ }
+
+ /**
+ * This method is invoked before validation starts.
+ * The default implementation raises a `beforeValidate` event.
+ * You may override this method to do preliminary checks before validation.
+ * Make sure the parent implementation is invoked so that the event can be raised.
+ * @return boolean whether the validation should be executed. Defaults to true.
+ * If false is returned, the validation will stop and the model is considered invalid.
+ */
+ public function beforeValidate()
+ {
+ $event = new ModelEvent;
+ $this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
+
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked after validation ends.
+ * The default implementation raises an `afterValidate` event.
+ * You may override this method to do postprocessing after validation.
+ * Make sure the parent implementation is invoked so that the event can be raised.
+ */
+ public function afterValidate()
+ {
+ $this->trigger(self::EVENT_AFTER_VALIDATE);
+ }
+
+ /**
+ * Returns all the validators declared in [[rules()]].
+ *
+ * This method differs from [[getActiveValidators()]] in that the latter
+ * only returns the validators applicable to the current [[scenario]].
+ *
+ * Because this method returns an ArrayObject object, you may
+ * manipulate it by inserting or removing validators (useful in model behaviors).
+ * For example,
+ *
+ * ~~~
+ * $model->validators[] = $newValidator;
+ * ~~~
+ *
+ * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model.
+ */
+ public function getValidators()
+ {
+ if ($this->_validators === null) {
+ $this->_validators = $this->createValidators();
+ }
+
+ return $this->_validators;
+ }
+
+ /**
+ * Returns the validators applicable to the current [[scenario]].
+ * @param string $attribute the name of the attribute whose applicable validators should be returned.
+ * If this is null, the validators for ALL attributes in the model will be returned.
+ * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
+ */
+ public function getActiveValidators($attribute = null)
+ {
+ $validators = [];
+ $scenario = $this->getScenario();
+ foreach ($this->getValidators() as $validator) {
+ if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
+ $validators[] = $validator;
+ }
+ }
+
+ return $validators;
+ }
+
+ /**
+ * Creates validator objects based on the validation rules specified in [[rules()]].
+ * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
+ * @return ArrayObject validators
+ * @throws InvalidConfigException if any validation rule configuration is invalid
+ */
+ public function createValidators()
+ {
+ $validators = new ArrayObject;
+ foreach ($this->rules() as $rule) {
+ if ($rule instanceof Validator) {
+ $validators->append($rule);
+ } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
+ $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
+ $validators->append($validator);
+ } else {
+ throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
+ }
+ }
+
+ return $validators;
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is required.
+ * This is determined by checking if the attribute is associated with a
+ * [[\yii\validators\RequiredValidator|required]] validation rule in the
+ * current [[scenario]].
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is required
+ */
+ public function isAttributeRequired($attribute)
+ {
+ foreach ($this->getActiveValidators($attribute) as $validator) {
+ if ($validator instanceof RequiredValidator) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is safe for massive assignments.
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is safe for massive assignments
+ * @see safeAttributes()
+ */
+ public function isAttributeSafe($attribute)
+ {
+ return in_array($attribute, $this->safeAttributes(), true);
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is active in the current scenario.
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is active in the current scenario
+ * @see activeAttributes()
+ */
+ public function isAttributeActive($attribute)
+ {
+ return in_array($attribute, $this->activeAttributes(), true);
+ }
+
+ /**
+ * Returns the text label for the specified attribute.
+ * @param string $attribute the attribute name
+ * @return string the attribute label
+ * @see generateAttributeLabel()
+ * @see attributeLabels()
+ */
+ public function getAttributeLabel($attribute)
+ {
+ $labels = $this->attributeLabels();
+
+ return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
+ }
+
+ /**
+ * Returns a value indicating whether there is any validation error.
+ * @param string|null $attribute attribute name. Use null to check all attributes.
+ * @return boolean whether there is any error.
+ */
+ public function hasErrors($attribute = null)
+ {
+ return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
+ }
+
+ /**
+ * Returns the errors for all attribute or a single attribute.
+ * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
+ * @property array An array of errors for all attributes. Empty array is returned if no error.
+ * The result is a two-dimensional array. See [[getErrors()]] for detailed description.
+ * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
+ * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following:
+ *
+ * ~~~
+ * [
+ * 'username' => [
+ * 'Username is required.',
+ * 'Username must contain only word characters.',
+ * ],
+ * 'email' => [
+ * 'Email address is invalid.',
+ * ]
+ * ]
+ * ~~~
+ *
+ * @see getFirstErrors()
+ * @see getFirstError()
+ */
+ public function getErrors($attribute = null)
+ {
+ if ($attribute === null) {
+ return $this->_errors === null ? [] : $this->_errors;
+ } else {
+ return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
+ }
+ }
+
+ /**
+ * Returns the first error of every attribute in the model.
+ * @return array the first errors. The array keys are the attribute names, and the array
+ * values are the corresponding error messages. An empty array will be returned if there is no error.
+ * @see getErrors()
+ * @see getFirstError()
+ */
+ public function getFirstErrors()
+ {
+ if (empty($this->_errors)) {
+ return [];
+ } else {
+ $errors = [];
+ foreach ($this->_errors as $name => $es) {
+ if (!empty($es)) {
+ $errors[$name] = reset($es);
+ }
+ }
+
+ return $errors;
+ }
+ }
+
+ /**
+ * Returns the first error of the specified attribute.
+ * @param string $attribute attribute name.
+ * @return string the error message. Null is returned if no error.
+ * @see getErrors()
+ * @see getFirstErrors()
+ */
+ public function getFirstError($attribute)
+ {
+ return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
+ }
+
+ /**
+ * Adds a new error to the specified attribute.
+ * @param string $attribute attribute name
+ * @param string $error new error message
+ */
+ public function addError($attribute, $error = '')
+ {
+ $this->_errors[$attribute][] = $error;
+ }
+
+ /**
+ * Removes errors for all attributes or a single attribute.
+ * @param string $attribute attribute name. Use null to remove errors for all attribute.
+ */
+ public function clearErrors($attribute = null)
+ {
+ if ($attribute === null) {
+ $this->_errors = [];
+ } else {
+ unset($this->_errors[$attribute]);
+ }
+ }
+
+ /**
+ * Generates a user friendly attribute label based on the give attribute name.
+ * This is done by replacing underscores, dashes and dots with blanks and
+ * changing the first letter of each word to upper case.
+ * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'.
+ * @param string $name the column name
+ * @return string the attribute label
+ */
+ public function generateAttributeLabel($name)
+ {
+ return Inflector::camel2words($name, true);
+ }
+
+ /**
+ * Returns attribute values.
+ * @param array $names list of attributes whose value needs to be returned.
+ * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
+ * If it is an array, only the attributes in the array will be returned.
+ * @param array $except list of attributes whose value should NOT be returned.
+ * @return array attribute values (name => value).
+ */
+ public function getAttributes($names = null, $except = [])
+ {
+ $values = [];
+ if ($names === null) {
+ $names = $this->attributes();
+ }
+ foreach ($names as $name) {
+ $values[$name] = $this->$name;
+ }
+ foreach ($except as $name) {
+ unset($values[$name]);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Sets the attribute values in a massive way.
+ * @param array $values attribute values (name => value) to be assigned to the model.
+ * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
+ * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
+ * @see safeAttributes()
+ * @see attributes()
+ */
+ public function setAttributes($values, $safeOnly = true)
+ {
+ if (is_array($values)) {
+ $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
+ foreach ($values as $name => $value) {
+ if (isset($attributes[$name])) {
+ $this->$name = $value;
+ } elseif ($safeOnly) {
+ $this->onUnsafeAttribute($name, $value);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is invoked when an unsafe attribute is being massively assigned.
+ * The default implementation will log a warning message if YII_DEBUG is on.
+ * It does nothing otherwise.
+ * @param string $name the unsafe attribute name
+ * @param mixed $value the attribute value
+ */
+ public function onUnsafeAttribute($name, $value)
+ {
+ if (YII_DEBUG) {
+ Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
+ }
+ }
+
+ /**
+ * Returns the scenario that this model is used in.
+ *
+ * Scenario affects how validation is performed and which attributes can
+ * be massively assigned.
+ *
+ * @return string the scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
+ */
+ public function getScenario()
+ {
+ return $this->_scenario;
+ }
+
+ /**
+ * Sets the scenario for the model.
+ * Note that this method does not check if the scenario exists or not.
+ * The method [[validate()]] will perform this check.
+ * @param string $value the scenario that this model is in.
+ */
+ public function setScenario($value)
+ {
+ $this->_scenario = $value;
+ }
+
+ /**
+ * Returns the attribute names that are safe to be massively assigned in the current scenario.
+ * @return string[] safe attribute names
+ */
+ public function safeAttributes()
+ {
+ $scenario = $this->getScenario();
+ $scenarios = $this->scenarios();
+ if (!isset($scenarios[$scenario])) {
+ return [];
+ }
+ $attributes = [];
+ foreach ($scenarios[$scenario] as $attribute) {
+ if ($attribute[0] !== '!') {
+ $attributes[] = $attribute;
+ }
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Returns the attribute names that are subject to validation in the current scenario.
+ * @return string[] safe attribute names
+ */
+ public function activeAttributes()
+ {
+ $scenario = $this->getScenario();
+ $scenarios = $this->scenarios();
+ if (!isset($scenarios[$scenario])) {
+ return [];
+ }
+ $attributes = $scenarios[$scenario];
+ foreach ($attributes as $i => $attribute) {
+ if ($attribute[0] === '!') {
+ $attributes[$i] = substr($attribute, 1);
+ }
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Populates the model with the data from end user.
+ * The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]].
+ * If [[formName()]] is empty, the whole `$data` array will be used to populate the model.
+ * The data being populated is subject to the safety check by [[setAttributes()]].
+ * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
+ * supplied by end user.
+ * @param string $formName the form name to be used for loading the data into the model.
+ * If not set, [[formName()]] will be used.
+ * @return boolean whether the model is successfully populated with some data.
+ */
+ public function load($data, $formName = null)
+ {
+ $scope = $formName === null ? $this->formName() : $formName;
+ if ($scope == '' && !empty($data)) {
+ $this->setAttributes($data);
+
+ return true;
+ } elseif (isset($data[$scope])) {
+ $this->setAttributes($data[$scope]);
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Populates a set of models with the data from end user.
+ * This method is mainly used to collect tabular data input.
+ * The data to be loaded for each model is `$data[formName][index]`, where `formName`
+ * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
+ * If [[formName()]] is empty, `$data[index]` will be used to populate each model.
+ * The data being populated to each model is subject to the safety check by [[setAttributes()]].
+ * @param array $models the models to be populated. Note that all models should have the same class.
+ * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
+ * supplied by end user.
+ * @return boolean whether the model is successfully populated with some data.
+ */
+ public static function loadMultiple($models, $data)
+ {
+ /** @var Model $model */
+ $model = reset($models);
+ if ($model === false) {
+ return false;
+ }
+ $success = false;
+ $scope = $model->formName();
+ foreach ($models as $i => $model) {
+ if ($scope == '') {
+ if (isset($data[$i])) {
+ $model->setAttributes($data[$i]);
+ $success = true;
+ }
+ } elseif (isset($data[$scope][$i])) {
+ $model->setAttributes($data[$scope][$i]);
+ $success = true;
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Validates multiple models.
+ * This method will validate every model. The models being validated may
+ * be of the same or different types.
+ * @param array $models the models to be validated
+ * @param array $attributes list of attributes that should be validated.
+ * If this parameter is empty, it means any attribute listed in the applicable
+ * validation rules should be validated.
+ * @return boolean whether all models are valid. False will be returned if one
+ * or multiple models have validation error.
+ */
+ public static function validateMultiple($models, $attributes = null)
+ {
+ $valid = true;
+ /** @var Model $model */
+ foreach ($models as $model) {
+ $valid = $model->validate($attributes) && $valid;
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
+ *
+ * A field is a named element in the returned array by [[toArray()]].
+ *
+ * This method should return an array of field names or field definitions.
+ * If the former, the field name will be treated as an object property name whose value will be used
+ * as the field value. If the latter, the array key should be the field name while the array value should be
+ * the corresponding field definition which can be either an object property name or a PHP callable
+ * returning the corresponding field value. The signature of the callable should be:
+ *
+ * ```php
+ * function ($field, $model) {
+ * // return field value
+ * }
+ * ```
+ *
+ * For example, the following code declares four fields:
+ *
+ * - `email`: the field name is the same as the property name `email`;
+ * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
+ * values are obtained from the `first_name` and `last_name` properties;
+ * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
+ * and `last_name`.
+ *
+ * ```php
+ * return [
+ * 'email',
+ * 'firstName' => 'first_name',
+ * 'lastName' => 'last_name',
+ * 'fullName' => function () {
+ * return $this->first_name . ' ' . $this->last_name;
+ * },
+ * ];
+ * ```
+ *
+ * In this method, you may also want to return different lists of fields based on some context
+ * information. For example, depending on [[scenario]] or the privilege of the current application user,
+ * you may return different sets of visible fields or filter out some fields.
+ *
+ * The default implementation of this method returns [[attributes()]] indexed by the same attribute names.
+ *
+ * @return array the list of field names or field definitions.
+ * @see toArray()
+ */
+ public function fields()
+ {
+ $fields = $this->attributes();
+
+ return array_combine($fields, $fields);
+ }
+
+ /**
+ * Determines which fields can be returned by [[toArray()]].
+ * This method will check the requested fields against those declared in [[fields()]] and [[extraFields()]]
+ * to determine which fields can be returned.
+ * @param array $fields the fields being requested for exporting
+ * @param array $expand the additional fields being requested for exporting
+ * @return array the list of fields to be exported. The array keys are the field names, and the array values
+ * are the corresponding object property names or PHP callables returning the field values.
+ */
+ protected function resolveFields(array $fields, array $expand)
+ {
+ $result = [];
+
+ foreach ($this->fields() as $field => $definition) {
+ if (is_integer($field)) {
+ $field = $definition;
+ }
+ if (empty($fields) || in_array($field, $fields, true)) {
+ $result[$field] = $definition;
+ }
+ }
+
+ if (empty($expand)) {
+ return $result;
+ }
+
+ foreach ($this->extraFields() as $field => $definition) {
+ if (is_integer($field)) {
+ $field = $definition;
+ }
+ if (in_array($field, $expand, true)) {
+ $result[$field] = $definition;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns an iterator for traversing the attributes in the model.
+ * This method is required by the interface IteratorAggregate.
+ * @return ArrayIterator an iterator for traversing the items in the list.
+ */
+ public function getIterator()
+ {
+ $attributes = $this->getAttributes();
+
+ return new ArrayIterator($attributes);
+ }
+
+ /**
+ * Returns whether there is an element at the specified offset.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `isset($model[$offset])`.
+ * @param mixed $offset the offset to check on
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return $this->$offset !== null;
+ }
+
+ /**
+ * Returns the element at the specified offset.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$value = $model[$offset];`.
+ * @param mixed $offset the offset to retrieve element.
+ * @return mixed the element at the offset, null if no element is found at the offset
+ */
+ public function offsetGet($offset)
+ {
+ return $this->$offset;
+ }
+
+ /**
+ * Sets the element at the specified offset.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$model[$offset] = $item;`.
+ * @param integer $offset the offset to set element
+ * @param mixed $item the element value
+ */
+ public function offsetSet($offset, $item)
+ {
+ $this->$offset = $item;
+ }
+
+ /**
+ * Sets the element value at the specified offset to null.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `unset($model[$offset])`.
+ * @param mixed $offset the offset to unset element
+ */
+ public function offsetUnset($offset)
+ {
+ $this->$offset = null;
+ }
}
diff --git a/framework/base/ModelEvent.php b/framework/base/ModelEvent.php
index 57e41f9ce30..13e03ea971b 100644
--- a/framework/base/ModelEvent.php
+++ b/framework/base/ModelEvent.php
@@ -17,9 +17,9 @@
*/
class ModelEvent extends Event
{
- /**
- * @var boolean whether the model is in valid status. Defaults to true.
- * A model is in valid status if it passes validations or certain checks.
- */
- public $isValid = true;
+ /**
+ * @var boolean whether the model is in valid status. Defaults to true.
+ * A model is in valid status if it passes validations or certain checks.
+ */
+ public $isValid = true;
}
diff --git a/framework/base/Module.php b/framework/base/Module.php
index eb7ce90a052..ef7ac0846fe 100644
--- a/framework/base/Module.php
+++ b/framework/base/Module.php
@@ -37,679 +37,689 @@
*/
class Module extends Component
{
- /**
- * @var array custom module parameters (name => value).
- */
- public $params = [];
- /**
- * @var array the IDs of the components or modules that should be preloaded right after initialization.
- */
- public $preload = [];
- /**
- * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
- */
- public $id;
- /**
- * @var Module the parent module of this module. Null if this module does not have a parent.
- */
- public $module;
- /**
- * @var string|boolean the layout that should be applied for views within this module. This refers to a view name
- * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
- * will be taken. If this is false, layout will be disabled within this module.
- */
- public $layout;
- /**
- * @var array mapping from controller ID to controller configurations.
- * Each name-value pair specifies the configuration of a single controller.
- * A controller configuration can be either a string or an array.
- * If the former, the string should be the fully qualified class name of the controller.
- * If the latter, the array must contain a 'class' element which specifies
- * the controller's fully qualified class name, and the rest of the name-value pairs
- * in the array are used to initialize the corresponding controller properties. For example,
- *
- * ~~~
- * [
- * 'account' => 'app\controllers\UserController',
- * 'article' => [
- * 'class' => 'app\controllers\PostController',
- * 'pageTitle' => 'something new',
- * ],
- * ]
- * ~~~
- */
- public $controllerMap = [];
- /**
- * @var string the namespace that controller classes are in. If not set,
- * it will use the "controllers" sub-namespace under the namespace of this module.
- * For example, if the namespace of this module is "foo\bar", then the default
- * controller namespace would be "foo\bar\controllers".
- */
- public $controllerNamespace;
- /**
- * @return string the default route of this module. Defaults to 'default'.
- * The route may consist of child module ID, controller ID, and/or action ID.
- * For example, `help`, `post/create`, `admin/post/create`.
- * If action ID is not given, it will take the default value as specified in
- * [[Controller::defaultAction]].
- */
- public $defaultRoute = 'default';
- /**
- * @var string the root directory of the module.
- */
- private $_basePath;
- /**
- * @var string the root directory that contains view files for this module
- */
- private $_viewPath;
- /**
- * @var string the root directory that contains layout view files for this module.
- */
- private $_layoutPath;
- /**
- * @var array child modules of this module
- */
- private $_modules = [];
- /**
- * @var array components registered under this module
- */
- private $_components = [];
-
- /**
- * Constructor.
- * @param string $id the ID of this module
- * @param Module $parent the parent module (if any)
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($id, $parent = null, $config = [])
- {
- $this->id = $id;
- $this->module = $parent;
- parent::__construct($config);
- }
-
- /**
- * Getter magic method.
- * This method is overridden to support accessing components
- * like reading module properties.
- * @param string $name component or property name
- * @return mixed the named property value
- */
- public function __get($name)
- {
- if ($this->hasComponent($name)) {
- return $this->getComponent($name);
- } else {
- return parent::__get($name);
- }
- }
-
- /**
- * Checks if a property value is null.
- * This method overrides the parent implementation by checking
- * if the named component is loaded.
- * @param string $name the property name or the event name
- * @return boolean whether the property value is null
- */
- public function __isset($name)
- {
- if ($this->hasComponent($name)) {
- return $this->getComponent($name) !== null;
- } else {
- return parent::__isset($name);
- }
- }
-
- /**
- * Initializes the module.
- * This method is called after the module is created and initialized with property values
- * given in configuration. The default implementation will call [[preloadComponents()]] to
- * load components that are declared in [[preload]].
- *
- * If you override this method, please make sure you call the parent implementation.
- */
- public function init()
- {
- if ($this->controllerNamespace === null) {
- $class = get_class($this);
- if (($pos = strrpos($class, '\\')) !== false) {
- $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers';
- }
- }
- $this->preloadComponents();
- }
-
- /**
- * Returns an ID that uniquely identifies this module among all modules within the current application.
- * Note that if the module is an application, an empty string will be returned.
- * @return string the unique ID of the module.
- */
- public function getUniqueId()
- {
- return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id;
- }
-
- /**
- * Returns the root directory of the module.
- * It defaults to the directory containing the module class file.
- * @return string the root directory of the module.
- */
- public function getBasePath()
- {
- if ($this->_basePath === null) {
- $class = new \ReflectionClass($this);
- $this->_basePath = dirname($class->getFileName());
- }
- return $this->_basePath;
- }
-
- /**
- * Sets the root directory of the module.
- * This method can only be invoked at the beginning of the constructor.
- * @param string $path the root directory of the module. This can be either a directory name or a path alias.
- * @throws InvalidParamException if the directory does not exist.
- */
- public function setBasePath($path)
- {
- $path = Yii::getAlias($path);
- $p = realpath($path);
- if ($p !== false && is_dir($p)) {
- $this->_basePath = $p;
- } else {
- throw new InvalidParamException("The directory does not exist: $path");
- }
- }
-
- /**
- * Returns the directory that contains the controller classes according to [[controllerNamespace]].
- * Note that in order for this method to return a value, you must define
- * an alias for the root namespace of [[controllerNamespace]].
- * @return string the directory that contains the controller classes.
- * @throws InvalidParamException if there is no alias defined for the root namespace of [[controllerNamespace]].
- */
- public function getControllerPath()
- {
- return Yii::getAlias('@' . str_replace('\\', '/', $this->controllerNamespace));
- }
-
- /**
- * Returns the directory that contains the view files for this module.
- * @return string the root directory of view files. Defaults to "[[basePath]]/view".
- */
- public function getViewPath()
- {
- if ($this->_viewPath !== null) {
- return $this->_viewPath;
- } else {
- return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
- }
- }
-
- /**
- * Sets the directory that contains the view files.
- * @param string $path the root directory of view files.
- * @throws InvalidParamException if the directory is invalid
- */
- public function setViewPath($path)
- {
- $this->_viewPath = Yii::getAlias($path);
- }
-
- /**
- * Returns the directory that contains layout view files for this module.
- * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
- */
- public function getLayoutPath()
- {
- if ($this->_layoutPath !== null) {
- return $this->_layoutPath;
- } else {
- return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
- }
- }
-
- /**
- * Sets the directory that contains the layout files.
- * @param string $path the root directory of layout files.
- * @throws InvalidParamException if the directory is invalid
- */
- public function setLayoutPath($path)
- {
- $this->_layoutPath = Yii::getAlias($path);
- }
-
- /**
- * Defines path aliases.
- * This method calls [[Yii::setAlias()]] to register the path aliases.
- * This method is provided so that you can define path aliases when configuring a module.
- * @property array list of path aliases to be defined. The array keys are alias names
- * (must start with '@') and the array values are the corresponding paths or aliases.
- * See [[setAliases()]] for an example.
- * @param array $aliases list of path aliases to be defined. The array keys are alias names
- * (must start with '@') and the array values are the corresponding paths or aliases.
- * For example,
- *
- * ~~~
- * [
- * '@models' => '@app/models', // an existing alias
- * '@backend' => __DIR__ . '/../backend', // a directory
- * ]
- * ~~~
- */
- public function setAliases($aliases)
- {
- foreach ($aliases as $name => $alias) {
- Yii::setAlias($name, $alias);
- }
- }
-
- /**
- * Checks whether the child module of the specified ID exists.
- * This method supports checking the existence of both child and grand child modules.
- * @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`).
- * @return boolean whether the named module exists. Both loaded and unloaded modules
- * are considered.
- */
- public function hasModule($id)
- {
- if (($pos = strpos($id, '/')) !== false) {
- // sub-module
- $module = $this->getModule(substr($id, 0, $pos));
- return $module === null ? false : $module->hasModule(substr($id, $pos + 1));
- } else {
- return isset($this->_modules[$id]);
- }
- }
-
- /**
- * Retrieves the child module of the specified ID.
- * This method supports retrieving both child modules and grand child modules.
- * @param string $id module ID (case-sensitive). To retrieve grand child modules,
- * use ID path relative to this module (e.g. `admin/content`).
- * @param boolean $load whether to load the module if it is not yet loaded.
- * @return Module|null the module instance, null if the module does not exist.
- * @see hasModule()
- */
- public function getModule($id, $load = true)
- {
- if (($pos = strpos($id, '/')) !== false) {
- // sub-module
- $module = $this->getModule(substr($id, 0, $pos));
- return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load);
- }
-
- if (isset($this->_modules[$id])) {
- if ($this->_modules[$id] instanceof Module) {
- return $this->_modules[$id];
- } elseif ($load) {
- Yii::trace("Loading module: $id", __METHOD__);
- if (is_array($this->_modules[$id]) && !isset($this->_modules[$id]['class'])) {
- $this->_modules[$id]['class'] = 'yii\base\Module';
- }
- return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
- }
- }
- return null;
- }
-
- /**
- * Adds a sub-module to this module.
- * @param string $id module ID
- * @param Module|array|null $module the sub-module to be added to this module. This can
- * be one of the followings:
- *
- * - a [[Module]] object
- * - a configuration array: when [[getModule()]] is called initially, the array
- * will be used to instantiate the sub-module
- * - null: the named sub-module will be removed from this module
- */
- public function setModule($id, $module)
- {
- if ($module === null) {
- unset($this->_modules[$id]);
- } else {
- $this->_modules[$id] = $module;
- }
- }
-
- /**
- * Returns the sub-modules in this module.
- * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
- * then all sub-modules registered in this module will be returned, whether they are loaded or not.
- * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
- * @return array the modules (indexed by their IDs)
- */
- public function getModules($loadedOnly = false)
- {
- if ($loadedOnly) {
- $modules = [];
- foreach ($this->_modules as $module) {
- if ($module instanceof Module) {
- $modules[] = $module;
- }
- }
- return $modules;
- } else {
- return $this->_modules;
- }
- }
-
- /**
- * Registers sub-modules in the current module.
- *
- * Each sub-module should be specified as a name-value pair, where
- * name refers to the ID of the module and value the module or a configuration
- * array that can be used to create the module. In the latter case, [[Yii::createObject()]]
- * will be used to create the module.
- *
- * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
- *
- * The following is an example for registering two sub-modules:
- *
- * ~~~
- * [
- * 'comment' => [
- * 'class' => 'app\modules\comment\CommentModule',
- * 'db' => 'db',
- * ],
- * 'booking' => ['class' => 'app\modules\booking\BookingModule'],
- * ]
- * ~~~
- *
- * @param array $modules modules (id => module configuration or instances)
- */
- public function setModules($modules)
- {
- foreach ($modules as $id => $module) {
- $this->_modules[$id] = $module;
- }
- }
-
- /**
- * Checks whether the named component exists.
- * @param string $id component ID
- * @return boolean whether the named component exists. Both loaded and unloaded components
- * are considered.
- */
- public function hasComponent($id)
- {
- return isset($this->_components[$id]);
- }
-
- /**
- * Retrieves the named component.
- * @param string $id component ID (case-sensitive)
- * @param boolean $load whether to load the component if it is not yet loaded.
- * @return Component|null the component instance, null if the component does not exist.
- * @see hasComponent()
- */
- public function getComponent($id, $load = true)
- {
- if (isset($this->_components[$id])) {
- if ($this->_components[$id] instanceof Object) {
- return $this->_components[$id];
- } elseif ($load) {
- return $this->_components[$id] = Yii::createObject($this->_components[$id]);
- }
- }
- return null;
- }
-
- /**
- * Registers a component with this module.
- * @param string $id component ID
- * @param Component|array|null $component the component to be registered with the module. This can
- * be one of the followings:
- *
- * - a [[Component]] object
- * - a configuration array: when [[getComponent()]] is called initially for this component, the array
- * will be used to instantiate the component via [[Yii::createObject()]].
- * - null: the named component will be removed from the module
- */
- public function setComponent($id, $component)
- {
- if ($component === null) {
- unset($this->_components[$id]);
- } else {
- $this->_components[$id] = $component;
- }
- }
-
- /**
- * Returns the registered components.
- * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
- * then all components specified in the configuration will be returned, whether they are loaded or not.
- * Loaded components will be returned as objects, while unloaded components as configuration arrays.
- * @return array the components (indexed by their IDs)
- */
- public function getComponents($loadedOnly = false)
- {
- if ($loadedOnly) {
- $components = [];
- foreach ($this->_components as $component) {
- if ($component instanceof Component) {
- $components[] = $component;
- }
- }
- return $components;
- } else {
- return $this->_components;
- }
- }
-
- /**
- * Registers a set of components in this module.
- *
- * Each component should be specified as a name-value pair, where
- * name refers to the ID of the component and value the component or a configuration
- * array that can be used to create the component. In the latter case, [[Yii::createObject()]]
- * will be used to create the component.
- *
- * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
- *
- * The following is an example for setting two components:
- *
- * ~~~
- * [
- * 'db' => [
- * 'class' => 'yii\db\Connection',
- * 'dsn' => 'sqlite:path/to/file.db',
- * ],
- * 'cache' => [
- * 'class' => 'yii\caching\DbCache',
- * 'db' => 'db',
- * ],
- * ]
- * ~~~
- *
- * @param array $components components (id => component configuration or instance)
- */
- public function setComponents($components)
- {
- foreach ($components as $id => $component) {
- if (!is_object($component) && isset($this->_components[$id]['class']) && !isset($component['class'])) {
- // set default component class
- $component['class'] = $this->_components[$id]['class'];
- }
- $this->_components[$id] = $component;
- }
- }
-
- /**
- * Loads components that are declared in [[preload]].
- * @throws InvalidConfigException if a component or module to be preloaded is unknown
- */
- public function preloadComponents()
- {
- foreach ($this->preload as $id) {
- if ($this->hasComponent($id)) {
- $this->getComponent($id);
- } elseif ($this->hasModule($id)) {
- $this->getModule($id);
- } else {
- throw new InvalidConfigException("Unknown component or module: $id");
- }
- }
- }
-
- /**
- * 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.
- * If the route is empty, the method will use [[defaultRoute]].
- * @param string $route the route that specifies the action.
- * @param array $params the parameters to be passed to the action
- * @return mixed the result of the action.
- * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
- */
- public function runAction($route, $params = [])
- {
- $parts = $this->createController($route);
- if (is_array($parts)) {
- /** @var Controller $controller */
- list($controller, $actionID) = $parts;
- $oldController = Yii::$app->controller;
- Yii::$app->controller = $controller;
- $result = $controller->runAction($actionID, $params);
- Yii::$app->controller = $oldController;
- return $result;
- } else {
- $id = $this->getUniqueId();
- throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
- }
- }
-
- /**
- * Creates a controller instance based on the given route.
- *
- * The route should be relative to this module. The method implements the following algorithm
- * to resolve the given route:
- *
- * 1. If the route is empty, use [[defaultRoute]];
- * 2. If the first segment of the route is a valid module ID as declared in [[modules]],
- * call the module's `createController()` with the rest part of the route;
- * 3. If the first segment of the route is found in [[controllerMap]], create a controller
- * based on the corresponding configuration found in [[controllerMap]];
- * 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController`
- * or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]].
- *
- * If any of the above steps resolves into a controller, it is returned together with the rest
- * part of the route which will be treated as the action ID. Otherwise, false will be returned.
- *
- * @param string $route the route consisting of module, controller and action IDs.
- * @return array|boolean If the controller is created successfully, it will be returned together
- * with the requested action ID. Otherwise false will be returned.
- * @throws InvalidConfigException if the controller class and its file do not match.
- */
- public function createController($route)
- {
- if ($route === '') {
- $route = $this->defaultRoute;
- }
-
- // double slashes or leading/ending slashes may cause substr problem
- $route = trim($route, '/');
- if (strpos($route, '//') !== false) {
- return false;
- }
-
- if (strpos($route, '/') !== false) {
- list ($id, $route) = explode('/', $route, 2);
- } else {
- $id = $route;
- $route = '';
- }
-
- // module and controller map take precedence
- $module = $this->getModule($id);
- if ($module !== null) {
- return $module->createController($route);
- }
- if (isset($this->controllerMap[$id])) {
- $controller = Yii::createObject($this->controllerMap[$id], $id, $this);
- return [$controller, $route];
- }
-
- if (($pos = strrpos($route, '/')) !== false) {
- $id .= '/' . substr($route, 0, $pos);
- $route = substr($route, $pos + 1);
- }
-
- $controller = $this->createControllerByID($id);
- if ($controller === null && $route !== '') {
- $controller = $this->createControllerByID($id . '/' . $route);
- $route = '';
- }
-
- return $controller === null ? false : [$controller, $route];
- }
-
- /**
- * Creates a controller based on the given controller ID.
- *
- * The controller ID is relative to this module. The controller class
- * should be namespaced under [[controllerNamespace]].
- *
- * Note that this method does not check [[modules]] or [[controllerMap]].
- *
- * @param string $id the controller ID
- * @return Controller the newly created controller instance, or null if the controller ID is invalid.
- * @throws InvalidConfigException if the controller class and its file name do not match.
- * This exception is only thrown when in debug mode.
- */
- public function createControllerByID($id)
- {
- if (!preg_match('%^[a-z0-9\\-_/]+$%', $id)) {
- return null;
- }
-
- $pos = strrpos($id, '/');
- if ($pos === false) {
- $prefix = '';
- $className = $id;
- } else {
- $prefix = substr($id, 0, $pos + 1);
- $className = substr($id, $pos + 1);
- }
-
- $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $className))) . 'Controller';
- $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
- if (strpos($className, '-') !== false || !class_exists($className)) {
- return null;
- }
-
- if (is_subclass_of($className, 'yii\base\Controller')) {
- return new $className($id, $this);
- } elseif (YII_DEBUG) {
- throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
- } else {
- return null;
- }
- }
-
- /**
- * This method is invoked right before an action of this module is to be executed (after all possible filters.)
- * You may override this method to do last-minute preparation for the action.
- * Make sure you call the parent implementation so that the relevant event is triggered.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- return true;
- }
-
- /**
- * This method is invoked right after an action of this module has been executed.
- * You may override this method to do some postprocessing for the action.
- * Make sure you call the parent implementation so that the relevant event is triggered.
- * Also make sure you return the action result, whether it is processed or not.
- * @param Action $action the action just executed.
- * @param mixed $result the action return result.
- * @return mixed the processed action result.
- */
- public function afterAction($action, $result)
- {
- return $result;
- }
+ /**
+ * @var array custom module parameters (name => value).
+ */
+ public $params = [];
+ /**
+ * @var array the IDs of the components or modules that should be preloaded right after initialization.
+ */
+ public $preload = [];
+ /**
+ * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
+ */
+ public $id;
+ /**
+ * @var Module the parent module of this module. Null if this module does not have a parent.
+ */
+ public $module;
+ /**
+ * @var string|boolean the layout that should be applied for views within this module. This refers to a view name
+ * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
+ * will be taken. If this is false, layout will be disabled within this module.
+ */
+ public $layout;
+ /**
+ * @var array mapping from controller ID to controller configurations.
+ * Each name-value pair specifies the configuration of a single controller.
+ * A controller configuration can be either a string or an array.
+ * If the former, the string should be the fully qualified class name of the controller.
+ * If the latter, the array must contain a 'class' element which specifies
+ * the controller's fully qualified class name, and the rest of the name-value pairs
+ * in the array are used to initialize the corresponding controller properties. For example,
+ *
+ * ~~~
+ * [
+ * 'account' => 'app\controllers\UserController',
+ * 'article' => [
+ * 'class' => 'app\controllers\PostController',
+ * 'pageTitle' => 'something new',
+ * ],
+ * ]
+ * ~~~
+ */
+ public $controllerMap = [];
+ /**
+ * @var string the namespace that controller classes are in. If not set,
+ * it will use the "controllers" sub-namespace under the namespace of this module.
+ * For example, if the namespace of this module is "foo\bar", then the default
+ * controller namespace would be "foo\bar\controllers".
+ */
+ public $controllerNamespace;
+ /**
+ * @return string the default route of this module. Defaults to 'default'.
+ * The route may consist of child module ID, controller ID, and/or action ID.
+ * For example, `help`, `post/create`, `admin/post/create`.
+ * If action ID is not given, it will take the default value as specified in
+ * [[Controller::defaultAction]].
+ */
+ public $defaultRoute = 'default';
+ /**
+ * @var string the root directory of the module.
+ */
+ private $_basePath;
+ /**
+ * @var string the root directory that contains view files for this module
+ */
+ private $_viewPath;
+ /**
+ * @var string the root directory that contains layout view files for this module.
+ */
+ private $_layoutPath;
+ /**
+ * @var array child modules of this module
+ */
+ private $_modules = [];
+ /**
+ * @var array components registered under this module
+ */
+ private $_components = [];
+
+ /**
+ * Constructor.
+ * @param string $id the ID of this module
+ * @param Module $parent the parent module (if any)
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $parent = null, $config = [])
+ {
+ $this->id = $id;
+ $this->module = $parent;
+ parent::__construct($config);
+ }
+
+ /**
+ * Getter magic method.
+ * This method is overridden to support accessing components
+ * like reading module properties.
+ * @param string $name component or property name
+ * @return mixed the named property value
+ */
+ public function __get($name)
+ {
+ if ($this->hasComponent($name)) {
+ return $this->getComponent($name);
+ } else {
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * Checks if a property value is null.
+ * This method overrides the parent implementation by checking
+ * if the named component is loaded.
+ * @param string $name the property name or the event name
+ * @return boolean whether the property value is null
+ */
+ public function __isset($name)
+ {
+ if ($this->hasComponent($name)) {
+ return $this->getComponent($name) !== null;
+ } else {
+ return parent::__isset($name);
+ }
+ }
+
+ /**
+ * Initializes the module.
+ * This method is called after the module is created and initialized with property values
+ * given in configuration. The default implementation will call [[preloadComponents()]] to
+ * load components that are declared in [[preload]].
+ *
+ * If you override this method, please make sure you call the parent implementation.
+ */
+ public function init()
+ {
+ if ($this->controllerNamespace === null) {
+ $class = get_class($this);
+ if (($pos = strrpos($class, '\\')) !== false) {
+ $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers';
+ }
+ }
+ $this->preloadComponents();
+ }
+
+ /**
+ * Returns an ID that uniquely identifies this module among all modules within the current application.
+ * Note that if the module is an application, an empty string will be returned.
+ * @return string the unique ID of the module.
+ */
+ public function getUniqueId()
+ {
+ return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id;
+ }
+
+ /**
+ * Returns the root directory of the module.
+ * It defaults to the directory containing the module class file.
+ * @return string the root directory of the module.
+ */
+ public function getBasePath()
+ {
+ if ($this->_basePath === null) {
+ $class = new \ReflectionClass($this);
+ $this->_basePath = dirname($class->getFileName());
+ }
+
+ return $this->_basePath;
+ }
+
+ /**
+ * Sets the root directory of the module.
+ * This method can only be invoked at the beginning of the constructor.
+ * @param string $path the root directory of the module. This can be either a directory name or a path alias.
+ * @throws InvalidParamException if the directory does not exist.
+ */
+ public function setBasePath($path)
+ {
+ $path = Yii::getAlias($path);
+ $p = realpath($path);
+ if ($p !== false && is_dir($p)) {
+ $this->_basePath = $p;
+ } else {
+ throw new InvalidParamException("The directory does not exist: $path");
+ }
+ }
+
+ /**
+ * Returns the directory that contains the controller classes according to [[controllerNamespace]].
+ * Note that in order for this method to return a value, you must define
+ * an alias for the root namespace of [[controllerNamespace]].
+ * @return string the directory that contains the controller classes.
+ * @throws InvalidParamException if there is no alias defined for the root namespace of [[controllerNamespace]].
+ */
+ public function getControllerPath()
+ {
+ return Yii::getAlias('@' . str_replace('\\', '/', $this->controllerNamespace));
+ }
+
+ /**
+ * Returns the directory that contains the view files for this module.
+ * @return string the root directory of view files. Defaults to "[[basePath]]/view".
+ */
+ public function getViewPath()
+ {
+ if ($this->_viewPath !== null) {
+ return $this->_viewPath;
+ } else {
+ return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the view files.
+ * @param string $path the root directory of view files.
+ * @throws InvalidParamException if the directory is invalid
+ */
+ public function setViewPath($path)
+ {
+ $this->_viewPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Returns the directory that contains layout view files for this module.
+ * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
+ */
+ public function getLayoutPath()
+ {
+ if ($this->_layoutPath !== null) {
+ return $this->_layoutPath;
+ } else {
+ return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the layout files.
+ * @param string $path the root directory of layout files.
+ * @throws InvalidParamException if the directory is invalid
+ */
+ public function setLayoutPath($path)
+ {
+ $this->_layoutPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Defines path aliases.
+ * This method calls [[Yii::setAlias()]] to register the path aliases.
+ * This method is provided so that you can define path aliases when configuring a module.
+ * @property array list of path aliases to be defined. The array keys are alias names
+ * (must start with '@') and the array values are the corresponding paths or aliases.
+ * See [[setAliases()]] for an example.
+ * @param array $aliases list of path aliases to be defined. The array keys are alias names
+ * (must start with '@') and the array values are the corresponding paths or aliases.
+ * For example,
+ *
+ * ~~~
+ * [
+ * '@models' => '@app/models', // an existing alias
+ * '@backend' => __DIR__ . '/../backend', // a directory
+ * ]
+ * ~~~
+ */
+ public function setAliases($aliases)
+ {
+ foreach ($aliases as $name => $alias) {
+ Yii::setAlias($name, $alias);
+ }
+ }
+
+ /**
+ * Checks whether the child module of the specified ID exists.
+ * This method supports checking the existence of both child and grand child modules.
+ * @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`).
+ * @return boolean whether the named module exists. Both loaded and unloaded modules
+ * are considered.
+ */
+ public function hasModule($id)
+ {
+ if (($pos = strpos($id, '/')) !== false) {
+ // sub-module
+ $module = $this->getModule(substr($id, 0, $pos));
+
+ return $module === null ? false : $module->hasModule(substr($id, $pos + 1));
+ } else {
+ return isset($this->_modules[$id]);
+ }
+ }
+
+ /**
+ * Retrieves the child module of the specified ID.
+ * This method supports retrieving both child modules and grand child modules.
+ * @param string $id module ID (case-sensitive). To retrieve grand child modules,
+ * use ID path relative to this module (e.g. `admin/content`).
+ * @param boolean $load whether to load the module if it is not yet loaded.
+ * @return Module|null the module instance, null if the module does not exist.
+ * @see hasModule()
+ */
+ public function getModule($id, $load = true)
+ {
+ if (($pos = strpos($id, '/')) !== false) {
+ // sub-module
+ $module = $this->getModule(substr($id, 0, $pos));
+
+ return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load);
+ }
+
+ if (isset($this->_modules[$id])) {
+ if ($this->_modules[$id] instanceof Module) {
+ return $this->_modules[$id];
+ } elseif ($load) {
+ Yii::trace("Loading module: $id", __METHOD__);
+ if (is_array($this->_modules[$id]) && !isset($this->_modules[$id]['class'])) {
+ $this->_modules[$id]['class'] = 'yii\base\Module';
+ }
+
+ return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds a sub-module to this module.
+ * @param string $id module ID
+ * @param Module|array|null $module the sub-module to be added to this module. This can
+ * be one of the followings:
+ *
+ * - a [[Module]] object
+ * - a configuration array: when [[getModule()]] is called initially, the array
+ * will be used to instantiate the sub-module
+ * - null: the named sub-module will be removed from this module
+ */
+ public function setModule($id, $module)
+ {
+ if ($module === null) {
+ unset($this->_modules[$id]);
+ } else {
+ $this->_modules[$id] = $module;
+ }
+ }
+
+ /**
+ * Returns the sub-modules in this module.
+ * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
+ * then all sub-modules registered in this module will be returned, whether they are loaded or not.
+ * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
+ * @return array the modules (indexed by their IDs)
+ */
+ public function getModules($loadedOnly = false)
+ {
+ if ($loadedOnly) {
+ $modules = [];
+ foreach ($this->_modules as $module) {
+ if ($module instanceof Module) {
+ $modules[] = $module;
+ }
+ }
+
+ return $modules;
+ } else {
+ return $this->_modules;
+ }
+ }
+
+ /**
+ * Registers sub-modules in the current module.
+ *
+ * Each sub-module should be specified as a name-value pair, where
+ * name refers to the ID of the module and value the module or a configuration
+ * array that can be used to create the module. In the latter case, [[Yii::createObject()]]
+ * will be used to create the module.
+ *
+ * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
+ *
+ * The following is an example for registering two sub-modules:
+ *
+ * ~~~
+ * [
+ * 'comment' => [
+ * 'class' => 'app\modules\comment\CommentModule',
+ * 'db' => 'db',
+ * ],
+ * 'booking' => ['class' => 'app\modules\booking\BookingModule'],
+ * ]
+ * ~~~
+ *
+ * @param array $modules modules (id => module configuration or instances)
+ */
+ public function setModules($modules)
+ {
+ foreach ($modules as $id => $module) {
+ $this->_modules[$id] = $module;
+ }
+ }
+
+ /**
+ * Checks whether the named component exists.
+ * @param string $id component ID
+ * @return boolean whether the named component exists. Both loaded and unloaded components
+ * are considered.
+ */
+ public function hasComponent($id)
+ {
+ return isset($this->_components[$id]);
+ }
+
+ /**
+ * Retrieves the named component.
+ * @param string $id component ID (case-sensitive)
+ * @param boolean $load whether to load the component if it is not yet loaded.
+ * @return Component|null the component instance, null if the component does not exist.
+ * @see hasComponent()
+ */
+ public function getComponent($id, $load = true)
+ {
+ if (isset($this->_components[$id])) {
+ if ($this->_components[$id] instanceof Object) {
+ return $this->_components[$id];
+ } elseif ($load) {
+ return $this->_components[$id] = Yii::createObject($this->_components[$id]);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Registers a component with this module.
+ * @param string $id component ID
+ * @param Component|array|null $component the component to be registered with the module. This can
+ * be one of the followings:
+ *
+ * - a [[Component]] object
+ * - a configuration array: when [[getComponent()]] is called initially for this component, the array
+ * will be used to instantiate the component via [[Yii::createObject()]].
+ * - null: the named component will be removed from the module
+ */
+ public function setComponent($id, $component)
+ {
+ if ($component === null) {
+ unset($this->_components[$id]);
+ } else {
+ $this->_components[$id] = $component;
+ }
+ }
+
+ /**
+ * Returns the registered components.
+ * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
+ * then all components specified in the configuration will be returned, whether they are loaded or not.
+ * Loaded components will be returned as objects, while unloaded components as configuration arrays.
+ * @return array the components (indexed by their IDs)
+ */
+ public function getComponents($loadedOnly = false)
+ {
+ if ($loadedOnly) {
+ $components = [];
+ foreach ($this->_components as $component) {
+ if ($component instanceof Component) {
+ $components[] = $component;
+ }
+ }
+
+ return $components;
+ } else {
+ return $this->_components;
+ }
+ }
+
+ /**
+ * Registers a set of components in this module.
+ *
+ * Each component should be specified as a name-value pair, where
+ * name refers to the ID of the component and value the component or a configuration
+ * array that can be used to create the component. In the latter case, [[Yii::createObject()]]
+ * will be used to create the component.
+ *
+ * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
+ *
+ * The following is an example for setting two components:
+ *
+ * ~~~
+ * [
+ * 'db' => [
+ * 'class' => 'yii\db\Connection',
+ * 'dsn' => 'sqlite:path/to/file.db',
+ * ],
+ * 'cache' => [
+ * 'class' => 'yii\caching\DbCache',
+ * 'db' => 'db',
+ * ],
+ * ]
+ * ~~~
+ *
+ * @param array $components components (id => component configuration or instance)
+ */
+ public function setComponents($components)
+ {
+ foreach ($components as $id => $component) {
+ if (!is_object($component) && isset($this->_components[$id]['class']) && !isset($component['class'])) {
+ // set default component class
+ $component['class'] = $this->_components[$id]['class'];
+ }
+ $this->_components[$id] = $component;
+ }
+ }
+
+ /**
+ * Loads components that are declared in [[preload]].
+ * @throws InvalidConfigException if a component or module to be preloaded is unknown
+ */
+ public function preloadComponents()
+ {
+ foreach ($this->preload as $id) {
+ if ($this->hasComponent($id)) {
+ $this->getComponent($id);
+ } elseif ($this->hasModule($id)) {
+ $this->getModule($id);
+ } else {
+ throw new InvalidConfigException("Unknown component or module: $id");
+ }
+ }
+ }
+
+ /**
+ * 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.
+ * If the route is empty, the method will use [[defaultRoute]].
+ * @param string $route the route that specifies the action.
+ * @param array $params the parameters to be passed to the action
+ * @return mixed the result of the action.
+ * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
+ */
+ public function runAction($route, $params = [])
+ {
+ $parts = $this->createController($route);
+ if (is_array($parts)) {
+ /** @var Controller $controller */
+ list($controller, $actionID) = $parts;
+ $oldController = Yii::$app->controller;
+ Yii::$app->controller = $controller;
+ $result = $controller->runAction($actionID, $params);
+ Yii::$app->controller = $oldController;
+
+ return $result;
+ } else {
+ $id = $this->getUniqueId();
+ throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
+ }
+ }
+
+ /**
+ * Creates a controller instance based on the given route.
+ *
+ * The route should be relative to this module. The method implements the following algorithm
+ * to resolve the given route:
+ *
+ * 1. If the route is empty, use [[defaultRoute]];
+ * 2. If the first segment of the route is a valid module ID as declared in [[modules]],
+ * call the module's `createController()` with the rest part of the route;
+ * 3. If the first segment of the route is found in [[controllerMap]], create a controller
+ * based on the corresponding configuration found in [[controllerMap]];
+ * 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController`
+ * or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]].
+ *
+ * If any of the above steps resolves into a controller, it is returned together with the rest
+ * part of the route which will be treated as the action ID. Otherwise, false will be returned.
+ *
+ * @param string $route the route consisting of module, controller and action IDs.
+ * @return array|boolean If the controller is created successfully, it will be returned together
+ * with the requested action ID. Otherwise false will be returned.
+ * @throws InvalidConfigException if the controller class and its file do not match.
+ */
+ public function createController($route)
+ {
+ if ($route === '') {
+ $route = $this->defaultRoute;
+ }
+
+ // double slashes or leading/ending slashes may cause substr problem
+ $route = trim($route, '/');
+ if (strpos($route, '//') !== false) {
+ return false;
+ }
+
+ if (strpos($route, '/') !== false) {
+ list ($id, $route) = explode('/', $route, 2);
+ } else {
+ $id = $route;
+ $route = '';
+ }
+
+ // module and controller map take precedence
+ $module = $this->getModule($id);
+ if ($module !== null) {
+ return $module->createController($route);
+ }
+ if (isset($this->controllerMap[$id])) {
+ $controller = Yii::createObject($this->controllerMap[$id], $id, $this);
+
+ return [$controller, $route];
+ }
+
+ if (($pos = strrpos($route, '/')) !== false) {
+ $id .= '/' . substr($route, 0, $pos);
+ $route = substr($route, $pos + 1);
+ }
+
+ $controller = $this->createControllerByID($id);
+ if ($controller === null && $route !== '') {
+ $controller = $this->createControllerByID($id . '/' . $route);
+ $route = '';
+ }
+
+ return $controller === null ? false : [$controller, $route];
+ }
+
+ /**
+ * Creates a controller based on the given controller ID.
+ *
+ * The controller ID is relative to this module. The controller class
+ * should be namespaced under [[controllerNamespace]].
+ *
+ * Note that this method does not check [[modules]] or [[controllerMap]].
+ *
+ * @param string $id the controller ID
+ * @return Controller the newly created controller instance, or null if the controller ID is invalid.
+ * @throws InvalidConfigException if the controller class and its file name do not match.
+ * This exception is only thrown when in debug mode.
+ */
+ public function createControllerByID($id)
+ {
+ if (!preg_match('%^[a-z0-9\\-_/]+$%', $id)) {
+ return null;
+ }
+
+ $pos = strrpos($id, '/');
+ if ($pos === false) {
+ $prefix = '';
+ $className = $id;
+ } else {
+ $prefix = substr($id, 0, $pos + 1);
+ $className = substr($id, $pos + 1);
+ }
+
+ $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $className))) . 'Controller';
+ $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
+ if (strpos($className, '-') !== false || !class_exists($className)) {
+ return null;
+ }
+
+ if (is_subclass_of($className, 'yii\base\Controller')) {
+ return new $className($id, $this);
+ } elseif (YII_DEBUG) {
+ throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * This method is invoked right before an action of this module is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * Make sure you call the parent implementation so that the relevant event is triggered.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ return true;
+ }
+
+ /**
+ * This method is invoked right after an action of this module has been executed.
+ * You may override this method to do some postprocessing for the action.
+ * Make sure you call the parent implementation so that the relevant event is triggered.
+ * Also make sure you return the action result, whether it is processed or not.
+ * @param Action $action the action just executed.
+ * @param mixed $result the action return result.
+ * @return mixed the processed action result.
+ */
+ public function afterAction($action, $result)
+ {
+ return $result;
+ }
}
diff --git a/framework/base/NotSupportedException.php b/framework/base/NotSupportedException.php
index 5123f435c0f..d5832c62209 100644
--- a/framework/base/NotSupportedException.php
+++ b/framework/base/NotSupportedException.php
@@ -15,11 +15,11 @@
*/
class NotSupportedException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Not Supported';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Not Supported';
+ }
}
diff --git a/framework/base/Object.php b/framework/base/Object.php
index 8f20136c246..f6bfb4c18f5 100644
--- a/framework/base/Object.php
+++ b/framework/base/Object.php
@@ -76,212 +76,212 @@
*/
class Object
{
- /**
- * @return string the fully qualified name of this class.
- */
- public static function className()
- {
- return get_called_class();
- }
+ /**
+ * @return string the fully qualified name of this class.
+ */
+ public static function className()
+ {
+ return get_called_class();
+ }
- /**
- * Constructor.
- * The default implementation does two things:
- *
- * - Initializes the object with the given configuration `$config`.
- * - Call [[init()]].
- *
- * If this method is overridden in a child class, it is recommended that
- *
- * - the last parameter of the constructor is a configuration array, like `$config` here.
- * - call the parent implementation at the end of the constructor.
- *
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($config = [])
- {
- if (!empty($config)) {
- Yii::configure($this, $config);
- }
- $this->init();
- }
+ /**
+ * Constructor.
+ * The default implementation does two things:
+ *
+ * - Initializes the object with the given configuration `$config`.
+ * - Call [[init()]].
+ *
+ * If this method is overridden in a child class, it is recommended that
+ *
+ * - the last parameter of the constructor is a configuration array, like `$config` here.
+ * - call the parent implementation at the end of the constructor.
+ *
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($config = [])
+ {
+ if (!empty($config)) {
+ Yii::configure($this, $config);
+ }
+ $this->init();
+ }
- /**
- * Initializes the object.
- * This method is invoked at the end of the constructor after the object is initialized with the
- * given configuration.
- */
- public function init()
- {
- }
+ /**
+ * Initializes the object.
+ * This method is invoked at the end of the constructor after the object is initialized with the
+ * given configuration.
+ */
+ public function init()
+ {
+ }
- /**
- * 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
- * @throws InvalidCallException if the property is write-only
- * @see __set()
- */
- public function __get($name)
- {
- $getter = 'get' . $name;
- if (method_exists($this, $getter)) {
- return $this->$getter();
- } elseif (method_exists($this, 'set' . $name)) {
- throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
- } else {
- throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
- }
- }
+ /**
+ * 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
+ * @throws InvalidCallException if the property is write-only
+ * @see __set()
+ */
+ public function __get($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ return $this->$getter();
+ } elseif (method_exists($this, 'set' . $name)) {
+ throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
- /**
- * Sets 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 `$object->property = $value;`.
- * @param string $name the property name or the event name
- * @param mixed $value the property value
- * @throws UnknownPropertyException if the property is not defined
- * @throws InvalidCallException if the property is read-only
- * @see __get()
- */
- public function __set($name, $value)
- {
- $setter = 'set' . $name;
- if (method_exists($this, $setter)) {
- $this->$setter($value);
- } elseif (method_exists($this, 'get' . $name)) {
- throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
- } else {
- throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
- }
- }
+ /**
+ * Sets 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 `$object->property = $value;`.
+ * @param string $name the property name or the event name
+ * @param mixed $value the property value
+ * @throws UnknownPropertyException if the property is not defined
+ * @throws InvalidCallException if the property is read-only
+ * @see __get()
+ */
+ public function __set($name, $value)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ } elseif (method_exists($this, 'get' . $name)) {
+ throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
- /**
- * Checks if the named property is set (not null).
- *
- * Do not call this method directly as it is a PHP magic method that
- * will be implicitly called when executing `isset($object->property)`.
- *
- * Note that if the property is not defined, false will be returned.
- * @param string $name the property name or the event name
- * @return boolean whether the named property is set (not null).
- */
- public function __isset($name)
- {
- $getter = 'get' . $name;
- if (method_exists($this, $getter)) {
- return $this->$getter() !== null;
- } else {
- return false;
- }
- }
+ /**
+ * Checks if the named property is set (not null).
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `isset($object->property)`.
+ *
+ * Note that if the property is not defined, false will be returned.
+ * @param string $name the property name or the event name
+ * @return boolean whether the named property is set (not null).
+ */
+ public function __isset($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ return $this->$getter() !== null;
+ } else {
+ return false;
+ }
+ }
- /**
- * Sets an object property to null.
- *
- * Do not call this method directly as it is a PHP magic method that
- * will be implicitly called when executing `unset($object->property)`.
- *
- * Note that if the property is not defined, this method will do nothing.
- * If the property is read-only, it will throw an exception.
- * @param string $name the property name
- * @throws InvalidCallException if the property is read only.
- */
- public function __unset($name)
- {
- $setter = 'set' . $name;
- if (method_exists($this, $setter)) {
- $this->$setter(null);
- } elseif (method_exists($this, 'get' . $name)) {
- throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
- }
- }
+ /**
+ * Sets an object property to null.
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `unset($object->property)`.
+ *
+ * Note that if the property is not defined, this method will do nothing.
+ * If the property is read-only, it will throw an exception.
+ * @param string $name the property name
+ * @throws InvalidCallException if the property is read only.
+ */
+ public function __unset($name)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ $this->$setter(null);
+ } elseif (method_exists($this, 'get' . $name)) {
+ throw new InvalidCallException('Unsetting read-only 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)
- {
- throw new UnknownMethodException('Unknown method: ' . 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)
+ {
+ throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
+ }
- /**
- * Returns a value indicating whether a property is defined.
- * A property is defined if:
- *
- * - the class has a getter or setter method associated with the specified name
- * (in this case, property name is case-insensitive);
- * - the class has a member variable with the specified name (when `$checkVars` is true);
- *
- * @param string $name the property name
- * @param boolean $checkVars whether to treat member variables as properties
- * @return boolean whether the property is defined
- * @see canGetProperty()
- * @see canSetProperty()
- */
- public function hasProperty($name, $checkVars = true)
- {
- return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
- }
+ /**
+ * Returns a value indicating whether a property is defined.
+ * A property is defined if:
+ *
+ * - the class has a getter or setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @return boolean whether the property is defined
+ * @see canGetProperty()
+ * @see canSetProperty()
+ */
+ public function hasProperty($name, $checkVars = true)
+ {
+ return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
+ }
- /**
- * Returns a value indicating whether a property can be read.
- * A property is readable if:
- *
- * - the class has a getter method associated with the specified name
- * (in this case, property name is case-insensitive);
- * - the class has a member variable with the specified name (when `$checkVars` is true);
- *
- * @param string $name the property name
- * @param boolean $checkVars whether to treat member variables as properties
- * @return boolean whether the property can be read
- * @see canSetProperty()
- */
- public function canGetProperty($name, $checkVars = true)
- {
- return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
- }
+ /**
+ * Returns a value indicating whether a property can be read.
+ * A property is readable if:
+ *
+ * - the class has a getter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @return boolean whether the property can be read
+ * @see canSetProperty()
+ */
+ public function canGetProperty($name, $checkVars = true)
+ {
+ return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
+ }
- /**
- * Returns a value indicating whether a property can be set.
- * A property is writable if:
- *
- * - the class has a setter method associated with the specified name
- * (in this case, property name is case-insensitive);
- * - the class has a member variable with the specified name (when `$checkVars` is true);
- *
- * @param string $name the property name
- * @param boolean $checkVars whether to treat member variables as properties
- * @return boolean whether the property can be written
- * @see canGetProperty()
- */
- public function canSetProperty($name, $checkVars = true)
- {
- return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
- }
+ /**
+ * Returns a value indicating whether a property can be set.
+ * A property is writable if:
+ *
+ * - the class has a setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @return boolean whether the property can be written
+ * @see canGetProperty()
+ */
+ public function canSetProperty($name, $checkVars = true)
+ {
+ return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
+ }
- /**
- * Returns a value indicating whether a method is defined.
- *
- * The default implementation is a call to php function `method_exists()`.
- * You may override this method when you implemented the php magic method `__call()`.
- * @param string $name the property name
- * @return boolean whether the property is defined
- */
- public function hasMethod($name)
- {
- return method_exists($this, $name);
- }
+ /**
+ * Returns a value indicating whether a method is defined.
+ *
+ * The default implementation is a call to php function `method_exists()`.
+ * You may override this method when you implemented the php magic method `__call()`.
+ * @param string $name the property name
+ * @return boolean whether the property is defined
+ */
+ public function hasMethod($name)
+ {
+ return method_exists($this, $name);
+ }
}
diff --git a/framework/base/Request.php b/framework/base/Request.php
index eb9f805f6d2..8f8531daba0 100644
--- a/framework/base/Request.php
+++ b/framework/base/Request.php
@@ -20,65 +20,66 @@
*/
abstract class Request extends Component
{
- private $_scriptFile;
- private $_isConsoleRequest;
+ private $_scriptFile;
+ private $_isConsoleRequest;
- /**
- * Resolves the current request into a route and the associated parameters.
- * @return array the first element is the route, and the second is the associated parameters.
- */
- abstract public function resolve();
+ /**
+ * Resolves the current request into a route and the associated parameters.
+ * @return array the first element is the route, and the second is the associated parameters.
+ */
+ abstract public function resolve();
- /**
- * Returns a value indicating whether the current request is made via command line
- * @return boolean the value indicating whether the current request is made via console
- */
- public function getIsConsoleRequest()
- {
- return $this->_isConsoleRequest !== null ? $this->_isConsoleRequest : PHP_SAPI === 'cli';
- }
+ /**
+ * Returns a value indicating whether the current request is made via command line
+ * @return boolean the value indicating whether the current request is made via console
+ */
+ public function getIsConsoleRequest()
+ {
+ return $this->_isConsoleRequest !== null ? $this->_isConsoleRequest : PHP_SAPI === 'cli';
+ }
- /**
- * Sets the value indicating whether the current request is made via command line
- * @param boolean $value the value indicating whether the current request is made via command line
- */
- public function setIsConsoleRequest($value)
- {
- $this->_isConsoleRequest = $value;
- }
+ /**
+ * Sets the value indicating whether the current request is made via command line
+ * @param boolean $value the value indicating whether the current request is made via command line
+ */
+ public function setIsConsoleRequest($value)
+ {
+ $this->_isConsoleRequest = $value;
+ }
- /**
- * Returns entry script file path.
- * @return string entry script file path (processed w/ realpath())
- * @throws InvalidConfigException if the entry script file path cannot be determined automatically.
- */
- public function getScriptFile()
- {
- if ($this->_scriptFile === null) {
- if (isset($_SERVER['SCRIPT_FILENAME'])) {
- $this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
- } else {
- throw new InvalidConfigException('Unable to determine the entry script file path.');
- }
- }
- return $this->_scriptFile;
- }
+ /**
+ * Returns entry script file path.
+ * @return string entry script file path (processed w/ realpath())
+ * @throws InvalidConfigException if the entry script file path cannot be determined automatically.
+ */
+ public function getScriptFile()
+ {
+ if ($this->_scriptFile === null) {
+ if (isset($_SERVER['SCRIPT_FILENAME'])) {
+ $this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
+ } else {
+ throw new InvalidConfigException('Unable to determine the entry script file path.');
+ }
+ }
- /**
- * Sets the entry script file path.
- * The entry script file path can normally be determined based on the `SCRIPT_FILENAME` SERVER variable.
- * However, for some server configurations, this may not be correct or feasible.
- * This setter is provided so that the entry script file path can be manually specified.
- * @param string $value the entry script file path. This can be either a file path or a path alias.
- * @throws InvalidConfigException if the provided entry script file path is invalid.
- */
- public function setScriptFile($value)
- {
- $scriptFile = realpath(Yii::getAlias($value));
- if ($scriptFile !== false && is_file($scriptFile)) {
- $this->_scriptFile = $scriptFile;
- } else {
- throw new InvalidConfigException('Unable to determine the entry script file path.');
- }
- }
+ return $this->_scriptFile;
+ }
+
+ /**
+ * Sets the entry script file path.
+ * The entry script file path can normally be determined based on the `SCRIPT_FILENAME` SERVER variable.
+ * However, for some server configurations, this may not be correct or feasible.
+ * This setter is provided so that the entry script file path can be manually specified.
+ * @param string $value the entry script file path. This can be either a file path or a path alias.
+ * @throws InvalidConfigException if the provided entry script file path is invalid.
+ */
+ public function setScriptFile($value)
+ {
+ $scriptFile = realpath(Yii::getAlias($value));
+ if ($scriptFile !== false && is_file($scriptFile)) {
+ $this->_scriptFile = $scriptFile;
+ } else {
+ throw new InvalidConfigException('Unable to determine the entry script file path.');
+ }
+ }
}
diff --git a/framework/base/Response.php b/framework/base/Response.php
index 12ec644fab5..d258f0f5e06 100644
--- a/framework/base/Response.php
+++ b/framework/base/Response.php
@@ -15,29 +15,29 @@
*/
class Response extends Component
{
- /**
- * @var integer the exit status. Exit statuses should be in the range 0 to 254.
- * The status 0 means the program terminates successfully.
- */
- public $exitStatus = 0;
+ /**
+ * @var integer the exit status. Exit statuses should be in the range 0 to 254.
+ * The status 0 means the program terminates successfully.
+ */
+ public $exitStatus = 0;
- /**
- * Sends the response to client.
- */
- public function send()
- {
- }
+ /**
+ * Sends the response to client.
+ */
+ public function send()
+ {
+ }
- /**
- * Removes all existing output buffers.
- */
- public function clearOutputBuffers()
- {
- // the following manual level counting is to deal with zlib.output_compression set to On
- for ($level = ob_get_level(); $level > 0; --$level) {
- if (!@ob_end_clean()) {
- ob_clean();
- }
- }
- }
+ /**
+ * Removes all existing output buffers.
+ */
+ public function clearOutputBuffers()
+ {
+ // the following manual level counting is to deal with zlib.output_compression set to On
+ for ($level = ob_get_level(); $level > 0; --$level) {
+ if (!@ob_end_clean()) {
+ ob_clean();
+ }
+ }
+ }
}
diff --git a/framework/base/Theme.php b/framework/base/Theme.php
index 63382ad6399..49de868defb 100644
--- a/framework/base/Theme.php
+++ b/framework/base/Theme.php
@@ -66,82 +66,82 @@
*/
class Theme extends Component
{
- /**
- * @var string the root path or path alias of this theme. All resources of this theme are located
- * under this directory. This property must be set if [[pathMap]] is not set.
- * @see pathMap
- */
- public $basePath;
- /**
- * @var string the base URL (or path alias) for this theme. All resources of this theme are considered
- * to be under this base URL. This property must be set. It is mainly used by [[getUrl()]].
- */
- public $baseUrl;
- /**
- * @var array the mapping between view directories and their corresponding themed versions.
- * If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]].
- * This property is used by [[applyTo()]] when a view is trying to apply the theme.
- * Path aliases can be used when specifying directories.
- */
- public $pathMap;
+ /**
+ * @var string the root path or path alias of this theme. All resources of this theme are located
+ * under this directory. This property must be set if [[pathMap]] is not set.
+ * @see pathMap
+ */
+ public $basePath;
+ /**
+ * @var string the base URL (or path alias) for this theme. All resources of this theme are considered
+ * to be under this base URL. This property must be set. It is mainly used by [[getUrl()]].
+ */
+ public $baseUrl;
+ /**
+ * @var array the mapping between view directories and their corresponding themed versions.
+ * If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]].
+ * This property is used by [[applyTo()]] when a view is trying to apply the theme.
+ * Path aliases can be used when specifying directories.
+ */
+ public $pathMap;
+ /**
+ * Initializes the theme.
+ * @throws InvalidConfigException if [[basePath]] is not set.
+ */
+ public function init()
+ {
+ parent::init();
- /**
- * Initializes the theme.
- * @throws InvalidConfigException if [[basePath]] is not set.
- */
- public function init()
- {
- parent::init();
+ if ($this->baseUrl === null) {
+ throw new InvalidConfigException('The "baseUrl" property must be set.');
+ } else {
+ $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
+ }
- if ($this->baseUrl === null) {
- throw new InvalidConfigException('The "baseUrl" property must be set.');
- } else {
- $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
- }
+ if (empty($this->pathMap)) {
+ if ($this->basePath !== null) {
+ $this->basePath = Yii::getAlias($this->basePath);
+ $this->pathMap = [Yii::$app->getBasePath() => [$this->basePath]];
+ } else {
+ throw new InvalidConfigException('The "basePath" property must be set.');
+ }
+ }
+ }
- if (empty($this->pathMap)) {
- if ($this->basePath !== null) {
- $this->basePath = Yii::getAlias($this->basePath);
- $this->pathMap = [Yii::$app->getBasePath() => [$this->basePath]];
- } else {
- throw new InvalidConfigException('The "basePath" property must be set.');
- }
- }
- }
+ /**
+ * Converts a file to a themed file if possible.
+ * If there is no corresponding themed file, the original file will be returned.
+ * @param string $path the file to be themed
+ * @return string the themed file, or the original file if the themed version is not available.
+ */
+ public function applyTo($path)
+ {
+ $path = FileHelper::normalizePath($path);
+ foreach ($this->pathMap as $from => $tos) {
+ $from = FileHelper::normalizePath(Yii::getAlias($from)) . DIRECTORY_SEPARATOR;
+ if (strpos($path, $from) === 0) {
+ $n = strlen($from);
+ foreach ((array) $tos as $to) {
+ $to = FileHelper::normalizePath(Yii::getAlias($to)) . DIRECTORY_SEPARATOR;
+ $file = $to . substr($path, $n);
+ if (is_file($file)) {
+ return $file;
+ }
+ }
+ }
+ }
- /**
- * Converts a file to a themed file if possible.
- * If there is no corresponding themed file, the original file will be returned.
- * @param string $path the file to be themed
- * @return string the themed file, or the original file if the themed version is not available.
- */
- public function applyTo($path)
- {
- $path = FileHelper::normalizePath($path);
- foreach ($this->pathMap as $from => $tos) {
- $from = FileHelper::normalizePath(Yii::getAlias($from)) . DIRECTORY_SEPARATOR;
- if (strpos($path, $from) === 0) {
- $n = strlen($from);
- foreach ((array)$tos as $to) {
- $to = FileHelper::normalizePath(Yii::getAlias($to)) . DIRECTORY_SEPARATOR;
- $file = $to . substr($path, $n);
- if (is_file($file)) {
- return $file;
- }
- }
- }
- }
- return $path;
- }
+ return $path;
+ }
- /**
- * Converts a relative URL into an absolute URL using [[baseUrl]].
- * @param string $url the relative URL to be converted.
- * @return string the absolute URL
- */
- public function getUrl($url)
- {
- return $this->baseUrl . '/' . ltrim($url, '/');
- }
+ /**
+ * Converts a relative URL into an absolute URL using [[baseUrl]].
+ * @param string $url the relative URL to be converted.
+ * @return string the absolute URL
+ */
+ public function getUrl($url)
+ {
+ return $this->baseUrl . '/' . ltrim($url, '/');
+ }
}
diff --git a/framework/base/UnknownClassException.php b/framework/base/UnknownClassException.php
index b64c585c472..8f25353cdd5 100644
--- a/framework/base/UnknownClassException.php
+++ b/framework/base/UnknownClassException.php
@@ -15,11 +15,11 @@
*/
class UnknownClassException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Unknown Class';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Unknown Class';
+ }
}
diff --git a/framework/base/UnknownMethodException.php b/framework/base/UnknownMethodException.php
index 2277aff7e2b..5892a8f7844 100644
--- a/framework/base/UnknownMethodException.php
+++ b/framework/base/UnknownMethodException.php
@@ -15,11 +15,11 @@
*/
class UnknownMethodException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Unknown Method';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Unknown Method';
+ }
}
diff --git a/framework/base/UnknownPropertyException.php b/framework/base/UnknownPropertyException.php
index 0a12ce1d504..8f7eeaaa841 100644
--- a/framework/base/UnknownPropertyException.php
+++ b/framework/base/UnknownPropertyException.php
@@ -15,11 +15,11 @@
*/
class UnknownPropertyException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Unknown Property';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Unknown Property';
+ }
}
diff --git a/framework/base/View.php b/framework/base/View.php
index 67325660270..e0015314f73 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -26,453 +26,459 @@
*/
class View extends Component
{
- /**
- * @event Event an event that is triggered by [[beginPage()]].
- */
- const EVENT_BEGIN_PAGE = 'beginPage';
- /**
- * @event Event an event that is triggered by [[endPage()]].
- */
- const EVENT_END_PAGE = 'endPage';
- /**
- * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file.
- */
- const EVENT_BEFORE_RENDER = 'beforeRender';
- /**
- * @event ViewEvent an event that is triggered by [[renderFile()]] right after it renders a view file.
- */
- const EVENT_AFTER_RENDER = 'afterRender';
-
- /**
- * @var ViewContextInterface the context under which the [[renderFile()]] method is being invoked.
- */
- public $context;
- /**
- * @var mixed custom parameters that are shared among view templates.
- */
- public $params = [];
- /**
- * @var array a list of available renderers indexed by their corresponding supported file extensions.
- * Each renderer may be a view renderer object or the configuration for creating the renderer object.
- * For example, the following configuration enables both Smarty and Twig view renderers:
- *
- * ~~~
- * [
- * 'tpl' => ['class' => 'yii\smarty\ViewRenderer'],
- * 'twig' => ['class' => 'yii\twig\ViewRenderer'],
- * ]
- * ~~~
- *
- * If no renderer is available for the given view file, the view file will be treated as a normal PHP
- * and rendered via [[renderPhpFile()]].
- */
- public $renderers;
- /**
- * @var string the default view file extension. This will be appended to view file names if they don't have file extensions.
- */
- public $defaultExtension = 'php';
- /**
- * @var Theme|array the theme object or the configuration array for creating the theme object.
- * If not set, it means theming is not enabled.
- */
- public $theme;
- /**
- * @var array a list of named output blocks. The keys are the block names and the values
- * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]]
- * to capture small fragments of a view. They can be later accessed somewhere else
- * through this property.
- */
- public $blocks;
- /**
- * @var array a list of currently active fragment cache widgets. This property
- * is used internally to implement the content caching feature. Do not modify it directly.
- * @internal
- */
- public $cacheStack = [];
- /**
- * @var array a list of placeholders for embedding dynamic contents. This property
- * is used internally to implement the content caching feature. Do not modify it directly.
- * @internal
- */
- public $dynamicPlaceholders = [];
-
- /**
- * @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.
- */
- private $_viewFiles = [];
-
-
- /**
- * Initializes the view component.
- */
- public function init()
- {
- parent::init();
- if (is_array($this->theme)) {
- if (!isset($this->theme['class'])) {
- $this->theme['class'] = 'yii\base\Theme';
- }
- $this->theme = Yii::createObject($this->theme);
- }
- }
-
- /**
- * Renders a view.
- *
- * The view to be rendered can be specified in one of the following formats:
- *
- * - path alias (e.g. "@app/views/site/index");
- * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
- * 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()]].
- *
- * @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.
- * @return string the rendering result
- * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
- * @see renderFile()
- */
- public function render($view, $params = [], $context = null)
- {
- $viewFile = $this->findViewFile($view, $context);
- return $this->renderFile($viewFile, $params, $context);
- }
-
- /**
- * 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.
- * @return string the view file path. Note that the file may not exist.
- * @throws InvalidCallException if [[context]] is required and invalid.
- */
- protected function findViewFile($view, $context = null)
- {
- if (strncmp($view, '@', 1) === 0) {
- // e.g. "@app/views/main"
- $file = Yii::getAlias($view);
- } elseif (strncmp($view, '//', 2) === 0) {
- // e.g. "//layouts/main"
- $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- } elseif (strncmp($view, '/', 1) === 0) {
- // e.g. "/site/index"
- if (Yii::$app->controller !== null) {
- $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- } else {
- throw new InvalidCallException("Unable to locate view file for view '$view': no active controller.");
- }
- } 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.");
- }
- }
-
- if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
- return $file;
- }
- $path = $file . '.' . $this->defaultExtension;
- if ($this->defaultExtension !== 'php' && !is_file($path)) {
- $path = $file . '.php';
- }
- return $path;
- }
-
- /**
- * Renders a view file.
- *
- * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long
- * as it is available.
- *
- * The method will call [[FileHelper::localize()]] to localize the view file.
- *
- * If [[renderer]] is enabled (not null), the method will use it to render the view file.
- * Otherwise, it will simply include the view file as a normal PHP file, capture its output and
- * return it as a string.
- *
- * @param string $viewFile the view file. This can be either a file path or a path alias.
- * @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.
- * @return string the rendering result
- * @throws InvalidParamException if the view file does not exist
- */
- public function renderFile($viewFile, $params = [], $context = null)
- {
- $viewFile = Yii::getAlias($viewFile);
- if ($this->theme !== null) {
- $viewFile = $this->theme->applyTo($viewFile);
- }
- if (is_file($viewFile)) {
- $viewFile = FileHelper::localize($viewFile);
- } else {
- throw new InvalidParamException("The view file does not exist: $viewFile");
- }
-
- $oldContext = $this->context;
- if ($context !== null) {
- $this->context = $context;
- }
- $output = '';
- $this->_viewFiles[] = $viewFile;
-
- if ($this->beforeRender()) {
- Yii::trace("Rendering view file: $viewFile", __METHOD__);
- $ext = pathinfo($viewFile, PATHINFO_EXTENSION);
- if (isset($this->renderers[$ext])) {
- if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) {
- $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
- }
- /** @var ViewRenderer $renderer */
- $renderer = $this->renderers[$ext];
- $output = $renderer->render($this, $viewFile, $params);
- } else {
- $output = $this->renderPhpFile($viewFile, $params);
- }
- $this->afterRender($output);
- }
-
- array_pop($this->_viewFiles);
- $this->context = $oldContext;
-
- return $output;
- }
-
- /**
- * @return string|boolean the view file currently being rendered. False if no view file is being rendered.
- */
- public function getViewFile()
- {
- return end($this->_viewFiles);
- }
-
- /**
- * This method is invoked right before [[renderFile()]] renders a view file.
- * The default implementation will trigger the [[EVENT_BEFORE_RENDER]] event.
- * If you override this method, make sure you call the parent implementation first.
- * @return boolean whether to continue rendering the view file.
- */
- public function beforeRender()
- {
- $event = new ViewEvent;
- $this->trigger(self::EVENT_BEFORE_RENDER, $event);
- return $event->isValid;
- }
-
- /**
- * This method is invoked right after [[renderFile()]] renders a view file.
- * The default implementation will trigger the [[EVENT_AFTER_RENDER]] event.
- * If you override this method, make sure you call the parent implementation first.
- * @param string $output the rendering result of the view file. Updates to this parameter
- * will be passed back and returned by [[renderFile()]].
- */
- public function afterRender(&$output)
- {
- if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) {
- $event = new ViewEvent;
- $event->output = $output;
- $this->trigger(self::EVENT_AFTER_RENDER, $event);
- $output = $event->output;
- }
- }
-
- /**
- * Renders a view file as a PHP script.
- *
- * This method treats the view file as a PHP script and includes the file.
- * It extracts the given parameters and makes them available in the view file.
- * The method captures the output of the included view file and returns it as a string.
- *
- * This method should mainly be called by view renderer or [[renderFile()]].
- *
- * @param string $_file_ the view file.
- * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
- * @return string the rendering result
- */
- public function renderPhpFile($_file_, $_params_ = [])
- {
- ob_start();
- ob_implicit_flush(false);
- extract($_params_, EXTR_OVERWRITE);
- require($_file_);
- return ob_get_clean();
- }
-
- /**
- * Renders dynamic content returned by the given PHP statements.
- * This method is mainly used together with content caching (fragment caching and page caching)
- * when some portions of the content (called *dynamic content*) should not be cached.
- * The dynamic content must be returned by some PHP statements.
- * @param string $statements the PHP statements for generating the dynamic content.
- * @return string the placeholder of the dynamic content, or the dynamic content if there is no
- * active content cache currently.
- */
- public function renderDynamic($statements)
- {
- if (!empty($this->cacheStack)) {
- $n = count($this->dynamicPlaceholders);
- $placeholder = "";
- $this->addDynamicPlaceholder($placeholder, $statements);
- return $placeholder;
- } else {
- return $this->evaluateDynamicContent($statements);
- }
- }
-
- /**
- * Adds a placeholder for dynamic content.
- * This method is internally used.
- * @param string $placeholder the placeholder name
- * @param string $statements the PHP statements for generating the dynamic content
- */
- public function addDynamicPlaceholder($placeholder, $statements)
- {
- foreach ($this->cacheStack as $cache) {
- $cache->dynamicPlaceholders[$placeholder] = $statements;
- }
- $this->dynamicPlaceholders[$placeholder] = $statements;
- }
-
- /**
- * Evaluates the given PHP statements.
- * This method is mainly used internally to implement dynamic content feature.
- * @param string $statements the PHP statements to be evaluated.
- * @return mixed the return value of the PHP statements.
- */
- public function evaluateDynamicContent($statements)
- {
- return eval($statements);
- }
-
- /**
- * Begins recording a block.
- * This method is a shortcut to beginning [[Block]]
- * @param string $id the block ID.
- * @param boolean $renderInPlace whether to render the block content in place.
- * Defaults to false, meaning the captured block will not be displayed.
- * @return Block the Block widget instance
- */
- public function beginBlock($id, $renderInPlace = false)
- {
- return Block::begin([
- 'id' => $id,
- 'renderInPlace' => $renderInPlace,
- 'view' => $this,
- ]);
- }
-
- /**
- * Ends recording a block.
- */
- public function endBlock()
- {
- Block::end();
- }
-
- /**
- * Begins the rendering of content that is to be decorated by the specified view.
- * This method can be used to implement nested layout. For example, a layout can be embedded
- * in another layout file specified as '@app/views/layouts/base.php' like the following:
- *
- * ~~~
- * beginContent('@app/views/layouts/base.php'); ?>
- * ...layout content here...
- * endContent(); ?>
- * ~~~
- *
- * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget.
- * This can be specified as either the view file path or path alias.
- * @param array $params the variables (name => value) to be extracted and made available in the decorative view.
- * @return ContentDecorator the ContentDecorator widget instance
- * @see ContentDecorator
- */
- public function beginContent($viewFile, $params = [])
- {
- return ContentDecorator::begin([
- 'viewFile' => $viewFile,
- 'params' => $params,
- 'view' => $this,
- ]);
- }
-
- /**
- * Ends the rendering of content.
- */
- public function endContent()
- {
- ContentDecorator::end();
- }
-
- /**
- * Begins fragment caching.
- * This method will display cached content if it is available.
- * If not, it will start caching and would expect an [[endCache()]]
- * call to end the cache and save the content into cache.
- * A typical usage of fragment caching is as follows,
- *
- * ~~~
- * if ($this->beginCache($id)) {
- * // ...generate content here
- * $this->endCache();
- * }
- * ~~~
- *
- * @param string $id a unique ID identifying the fragment to be cached.
- * @param array $properties initial property values for [[FragmentCache]]
- * @return boolean whether you should generate the content for caching.
- * False if the cached version is available.
- */
- public function beginCache($id, $properties = [])
- {
- $properties['id'] = $id;
- $properties['view'] = $this;
- /** @var FragmentCache $cache */
- $cache = FragmentCache::begin($properties);
- if ($cache->getCachedContent() !== false) {
- $this->endCache();
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Ends fragment caching.
- */
- public function endCache()
- {
- FragmentCache::end();
- }
-
- /**
- * Marks the beginning of a page.
- */
- public function beginPage()
- {
- ob_start();
- ob_implicit_flush(false);
-
- $this->trigger(self::EVENT_BEGIN_PAGE);
- }
-
- /**
- * Marks the ending of a page.
- */
- public function endPage()
- {
- $this->trigger(self::EVENT_END_PAGE);
- ob_end_flush();
- }
+ /**
+ * @event Event an event that is triggered by [[beginPage()]].
+ */
+ const EVENT_BEGIN_PAGE = 'beginPage';
+ /**
+ * @event Event an event that is triggered by [[endPage()]].
+ */
+ const EVENT_END_PAGE = 'endPage';
+ /**
+ * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file.
+ */
+ const EVENT_BEFORE_RENDER = 'beforeRender';
+ /**
+ * @event ViewEvent an event that is triggered by [[renderFile()]] right after it renders a view file.
+ */
+ const EVENT_AFTER_RENDER = 'afterRender';
+
+ /**
+ * @var ViewContextInterface the context under which the [[renderFile()]] method is being invoked.
+ */
+ public $context;
+ /**
+ * @var mixed custom parameters that are shared among view templates.
+ */
+ public $params = [];
+ /**
+ * @var array a list of available renderers indexed by their corresponding supported file extensions.
+ * Each renderer may be a view renderer object or the configuration for creating the renderer object.
+ * For example, the following configuration enables both Smarty and Twig view renderers:
+ *
+ * ~~~
+ * [
+ * 'tpl' => ['class' => 'yii\smarty\ViewRenderer'],
+ * 'twig' => ['class' => 'yii\twig\ViewRenderer'],
+ * ]
+ * ~~~
+ *
+ * If no renderer is available for the given view file, the view file will be treated as a normal PHP
+ * and rendered via [[renderPhpFile()]].
+ */
+ public $renderers;
+ /**
+ * @var string the default view file extension. This will be appended to view file names if they don't have file extensions.
+ */
+ public $defaultExtension = 'php';
+ /**
+ * @var Theme|array the theme object or the configuration array for creating the theme object.
+ * If not set, it means theming is not enabled.
+ */
+ public $theme;
+ /**
+ * @var array a list of named output blocks. The keys are the block names and the values
+ * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]]
+ * to capture small fragments of a view. They can be later accessed somewhere else
+ * through this property.
+ */
+ public $blocks;
+ /**
+ * @var array a list of currently active fragment cache widgets. This property
+ * is used internally to implement the content caching feature. Do not modify it directly.
+ * @internal
+ */
+ public $cacheStack = [];
+ /**
+ * @var array a list of placeholders for embedding dynamic contents. This property
+ * is used internally to implement the content caching feature. Do not modify it directly.
+ * @internal
+ */
+ public $dynamicPlaceholders = [];
+
+ /**
+ * @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.
+ */
+ private $_viewFiles = [];
+
+
+ /**
+ * Initializes the view component.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_array($this->theme)) {
+ if (!isset($this->theme['class'])) {
+ $this->theme['class'] = 'yii\base\Theme';
+ }
+ $this->theme = Yii::createObject($this->theme);
+ }
+ }
+
+ /**
+ * Renders a view.
+ *
+ * The view to be rendered can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * 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()]].
+ *
+ * @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.
+ * @return string the rendering result
+ * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
+ * @see renderFile()
+ */
+ public function render($view, $params = [], $context = null)
+ {
+ $viewFile = $this->findViewFile($view, $context);
+
+ return $this->renderFile($viewFile, $params, $context);
+ }
+
+ /**
+ * 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.
+ * @return string the view file path. Note that the file may not exist.
+ * @throws InvalidCallException if [[context]] is required and invalid.
+ */
+ protected function findViewFile($view, $context = null)
+ {
+ if (strncmp($view, '@', 1) === 0) {
+ // e.g. "@app/views/main"
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '//', 2) === 0) {
+ // e.g. "//layouts/main"
+ $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } elseif (strncmp($view, '/', 1) === 0) {
+ // e.g. "/site/index"
+ if (Yii::$app->controller !== null) {
+ $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } else {
+ throw new InvalidCallException("Unable to locate view file for view '$view': no active controller.");
+ }
+ } 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.");
+ }
+ }
+
+ if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
+ return $file;
+ }
+ $path = $file . '.' . $this->defaultExtension;
+ if ($this->defaultExtension !== 'php' && !is_file($path)) {
+ $path = $file . '.php';
+ }
+
+ return $path;
+ }
+
+ /**
+ * Renders a view file.
+ *
+ * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long
+ * as it is available.
+ *
+ * The method will call [[FileHelper::localize()]] to localize the view file.
+ *
+ * If [[renderer]] is enabled (not null), the method will use it to render the view file.
+ * Otherwise, it will simply include the view file as a normal PHP file, capture its output and
+ * return it as a string.
+ *
+ * @param string $viewFile the view file. This can be either a file path or a path alias.
+ * @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.
+ * @return string the rendering result
+ * @throws InvalidParamException if the view file does not exist
+ */
+ public function renderFile($viewFile, $params = [], $context = null)
+ {
+ $viewFile = Yii::getAlias($viewFile);
+ if ($this->theme !== null) {
+ $viewFile = $this->theme->applyTo($viewFile);
+ }
+ if (is_file($viewFile)) {
+ $viewFile = FileHelper::localize($viewFile);
+ } else {
+ throw new InvalidParamException("The view file does not exist: $viewFile");
+ }
+
+ $oldContext = $this->context;
+ if ($context !== null) {
+ $this->context = $context;
+ }
+ $output = '';
+ $this->_viewFiles[] = $viewFile;
+
+ if ($this->beforeRender()) {
+ Yii::trace("Rendering view file: $viewFile", __METHOD__);
+ $ext = pathinfo($viewFile, PATHINFO_EXTENSION);
+ if (isset($this->renderers[$ext])) {
+ if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) {
+ $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
+ }
+ /** @var ViewRenderer $renderer */
+ $renderer = $this->renderers[$ext];
+ $output = $renderer->render($this, $viewFile, $params);
+ } else {
+ $output = $this->renderPhpFile($viewFile, $params);
+ }
+ $this->afterRender($output);
+ }
+
+ array_pop($this->_viewFiles);
+ $this->context = $oldContext;
+
+ return $output;
+ }
+
+ /**
+ * @return string|boolean the view file currently being rendered. False if no view file is being rendered.
+ */
+ public function getViewFile()
+ {
+ return end($this->_viewFiles);
+ }
+
+ /**
+ * This method is invoked right before [[renderFile()]] renders a view file.
+ * The default implementation will trigger the [[EVENT_BEFORE_RENDER]] event.
+ * If you override this method, make sure you call the parent implementation first.
+ * @return boolean whether to continue rendering the view file.
+ */
+ public function beforeRender()
+ {
+ $event = new ViewEvent;
+ $this->trigger(self::EVENT_BEFORE_RENDER, $event);
+
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked right after [[renderFile()]] renders a view file.
+ * The default implementation will trigger the [[EVENT_AFTER_RENDER]] event.
+ * If you override this method, make sure you call the parent implementation first.
+ * @param string $output the rendering result of the view file. Updates to this parameter
+ * will be passed back and returned by [[renderFile()]].
+ */
+ public function afterRender(&$output)
+ {
+ if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) {
+ $event = new ViewEvent;
+ $event->output = $output;
+ $this->trigger(self::EVENT_AFTER_RENDER, $event);
+ $output = $event->output;
+ }
+ }
+
+ /**
+ * Renders a view file as a PHP script.
+ *
+ * This method treats the view file as a PHP script and includes the file.
+ * It extracts the given parameters and makes them available in the view file.
+ * The method captures the output of the included view file and returns it as a string.
+ *
+ * This method should mainly be called by view renderer or [[renderFile()]].
+ *
+ * @param string $_file_ the view file.
+ * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @return string the rendering result
+ */
+ public function renderPhpFile($_file_, $_params_ = [])
+ {
+ ob_start();
+ ob_implicit_flush(false);
+ extract($_params_, EXTR_OVERWRITE);
+ require($_file_);
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Renders dynamic content returned by the given PHP statements.
+ * This method is mainly used together with content caching (fragment caching and page caching)
+ * when some portions of the content (called *dynamic content*) should not be cached.
+ * The dynamic content must be returned by some PHP statements.
+ * @param string $statements the PHP statements for generating the dynamic content.
+ * @return string the placeholder of the dynamic content, or the dynamic content if there is no
+ * active content cache currently.
+ */
+ public function renderDynamic($statements)
+ {
+ if (!empty($this->cacheStack)) {
+ $n = count($this->dynamicPlaceholders);
+ $placeholder = "";
+ $this->addDynamicPlaceholder($placeholder, $statements);
+
+ return $placeholder;
+ } else {
+ return $this->evaluateDynamicContent($statements);
+ }
+ }
+
+ /**
+ * Adds a placeholder for dynamic content.
+ * This method is internally used.
+ * @param string $placeholder the placeholder name
+ * @param string $statements the PHP statements for generating the dynamic content
+ */
+ public function addDynamicPlaceholder($placeholder, $statements)
+ {
+ foreach ($this->cacheStack as $cache) {
+ $cache->dynamicPlaceholders[$placeholder] = $statements;
+ }
+ $this->dynamicPlaceholders[$placeholder] = $statements;
+ }
+
+ /**
+ * Evaluates the given PHP statements.
+ * This method is mainly used internally to implement dynamic content feature.
+ * @param string $statements the PHP statements to be evaluated.
+ * @return mixed the return value of the PHP statements.
+ */
+ public function evaluateDynamicContent($statements)
+ {
+ return eval($statements);
+ }
+
+ /**
+ * Begins recording a block.
+ * This method is a shortcut to beginning [[Block]]
+ * @param string $id the block ID.
+ * @param boolean $renderInPlace whether to render the block content in place.
+ * Defaults to false, meaning the captured block will not be displayed.
+ * @return Block the Block widget instance
+ */
+ public function beginBlock($id, $renderInPlace = false)
+ {
+ return Block::begin([
+ 'id' => $id,
+ 'renderInPlace' => $renderInPlace,
+ 'view' => $this,
+ ]);
+ }
+
+ /**
+ * Ends recording a block.
+ */
+ public function endBlock()
+ {
+ Block::end();
+ }
+
+ /**
+ * Begins the rendering of content that is to be decorated by the specified view.
+ * This method can be used to implement nested layout. For example, a layout can be embedded
+ * in another layout file specified as '@app/views/layouts/base.php' like the following:
+ *
+ * ~~~
+ * beginContent('@app/views/layouts/base.php'); ?>
+ * ...layout content here...
+ * endContent(); ?>
+ * ~~~
+ *
+ * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget.
+ * This can be specified as either the view file path or path alias.
+ * @param array $params the variables (name => value) to be extracted and made available in the decorative view.
+ * @return ContentDecorator the ContentDecorator widget instance
+ * @see ContentDecorator
+ */
+ public function beginContent($viewFile, $params = [])
+ {
+ return ContentDecorator::begin([
+ 'viewFile' => $viewFile,
+ 'params' => $params,
+ 'view' => $this,
+ ]);
+ }
+
+ /**
+ * Ends the rendering of content.
+ */
+ public function endContent()
+ {
+ ContentDecorator::end();
+ }
+
+ /**
+ * Begins fragment caching.
+ * This method will display cached content if it is available.
+ * If not, it will start caching and would expect an [[endCache()]]
+ * call to end the cache and save the content into cache.
+ * A typical usage of fragment caching is as follows,
+ *
+ * ~~~
+ * if ($this->beginCache($id)) {
+ * // ...generate content here
+ * $this->endCache();
+ * }
+ * ~~~
+ *
+ * @param string $id a unique ID identifying the fragment to be cached.
+ * @param array $properties initial property values for [[FragmentCache]]
+ * @return boolean whether you should generate the content for caching.
+ * False if the cached version is available.
+ */
+ public function beginCache($id, $properties = [])
+ {
+ $properties['id'] = $id;
+ $properties['view'] = $this;
+ /** @var FragmentCache $cache */
+ $cache = FragmentCache::begin($properties);
+ if ($cache->getCachedContent() !== false) {
+ $this->endCache();
+
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Ends fragment caching.
+ */
+ public function endCache()
+ {
+ FragmentCache::end();
+ }
+
+ /**
+ * Marks the beginning of a page.
+ */
+ public function beginPage()
+ {
+ ob_start();
+ ob_implicit_flush(false);
+
+ $this->trigger(self::EVENT_BEGIN_PAGE);
+ }
+
+ /**
+ * Marks the ending of a page.
+ */
+ public function endPage()
+ {
+ $this->trigger(self::EVENT_END_PAGE);
+ ob_end_flush();
+ }
}
diff --git a/framework/base/ViewContextInterface.php b/framework/base/ViewContextInterface.php
index 94f6751c65a..12cf348fdf3 100644
--- a/framework/base/ViewContextInterface.php
+++ b/framework/base/ViewContextInterface.php
@@ -17,10 +17,10 @@
*/
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.
- */
- public function findViewFile($view);
+ /**
+ * 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.
+ */
+ public function findViewFile($view);
}
diff --git a/framework/base/ViewEvent.php b/framework/base/ViewEvent.php
index bad7264b242..123f55ea8d2 100644
--- a/framework/base/ViewEvent.php
+++ b/framework/base/ViewEvent.php
@@ -15,17 +15,17 @@
*/
class ViewEvent extends Event
{
- /**
- * @var string the rendering result of [[View::renderFile()]].
- * Event handlers may modify this property and the modified output will be
- * returned by [[View::renderFile()]]. This property is only used
- * by [[View::EVENT_AFTER_RENDER]] event.
- */
- public $output;
- /**
- * @var boolean whether to continue rendering the view file. Event handlers of
- * [[View::EVENT_BEFORE_RENDER]] may set this property to decide whether
- * to continue rendering the current view file.
- */
- public $isValid = true;
+ /**
+ * @var string the rendering result of [[View::renderFile()]].
+ * Event handlers may modify this property and the modified output will be
+ * returned by [[View::renderFile()]]. This property is only used
+ * by [[View::EVENT_AFTER_RENDER]] event.
+ */
+ public $output;
+ /**
+ * @var boolean whether to continue rendering the view file. Event handlers of
+ * [[View::EVENT_BEFORE_RENDER]] may set this property to decide whether
+ * to continue rendering the current view file.
+ */
+ public $isValid = true;
}
diff --git a/framework/base/ViewRenderer.php b/framework/base/ViewRenderer.php
index 576bbe89bd4..1ce152e52fb 100644
--- a/framework/base/ViewRenderer.php
+++ b/framework/base/ViewRenderer.php
@@ -15,16 +15,16 @@
*/
abstract class ViewRenderer extends Component
{
- /**
- * Renders a view file.
- *
- * This method is invoked by [[View]] whenever it tries to render a view.
- * Child classes must implement this method to render the given view file.
- *
- * @param View $view the view object used for rendering the file.
- * @param string $file the view file.
- * @param array $params the parameters to be passed to the view file.
- * @return string the rendering result
- */
- abstract public function render($view, $file, $params);
+ /**
+ * Renders a view file.
+ *
+ * This method is invoked by [[View]] whenever it tries to render a view.
+ * Child classes must implement this method to render the given view file.
+ *
+ * @param View $view the view object used for rendering the file.
+ * @param string $file the view file.
+ * @param array $params the parameters to be passed to the view file.
+ * @return string the rendering result
+ */
+ abstract public function render($view, $file, $params);
}
diff --git a/framework/base/Widget.php b/framework/base/Widget.php
index 2f45689af00..7effec161c0 100644
--- a/framework/base/Widget.php
+++ b/framework/base/Widget.php
@@ -24,192 +24,197 @@
*/
class Widget extends Component implements ViewContextInterface
{
- /**
- * @var integer a counter used to generate [[id]] for widgets.
- * @internal
- */
- public static $counter = 0;
- /**
- * @var string the prefix to the automatically generated widget IDs.
- * @see getId()
- */
- public static $autoIdPrefix = 'w';
-
- /**
- * @var Widget[] the widgets that are currently being rendered (not ended). This property
- * is maintained by [[begin()]] and [[end()]] methods.
- * @internal
- */
- public static $stack = [];
-
-
- /**
- * Begins a widget.
- * This method creates an instance of the calling class. It will apply the configuration
- * to the created instance. A matching [[end()]] call should be called later.
- * @param array $config name-value pairs that will be used to initialize the object properties
- * @return static the newly created widget instance
- */
- public static function begin($config = [])
- {
- $config['class'] = get_called_class();
- /** @var Widget $widget */
- $widget = Yii::createObject($config);
- self::$stack[] = $widget;
- return $widget;
- }
-
- /**
- * Ends a widget.
- * Note that the rendering result of the widget is directly echoed out.
- * @return static the widget instance that is ended.
- * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested
- */
- public static function end()
- {
- if (!empty(self::$stack)) {
- $widget = array_pop(self::$stack);
- if (get_class($widget) === get_called_class()) {
- $widget->run();
- return $widget;
- } else {
- throw new InvalidCallException("Expecting end() of " . get_class($widget) . ", found " . get_called_class());
- }
- } else {
- throw new InvalidCallException("Unexpected " . get_called_class() . '::end() call. A matching begin() is not found.');
- }
- }
-
- /**
- * Creates a widget instance and runs it.
- * The widget rendering result is returned by this method.
- * @param array $config name-value pairs that will be used to initialize the object properties
- * @return string the rendering result of the widget.
- */
- public static function widget($config = [])
- {
- ob_start();
- ob_implicit_flush(false);
- /** @var Widget $widget */
- $config['class'] = get_called_class();
- $widget = Yii::createObject($config);
- $out = $widget->run();
- return ob_get_clean() . $out;
- }
-
- private $_id;
-
- /**
- * Returns the ID of the widget.
- * @param boolean $autoGenerate whether to generate an ID if it is not set previously
- * @return string ID of the widget.
- */
- public function getId($autoGenerate = true)
- {
- if ($autoGenerate && $this->_id === null) {
- $this->_id = self::$autoIdPrefix . self::$counter++;
- }
- return $this->_id;
- }
-
- /**
- * Sets the ID of the widget.
- * @param string $value id of the widget.
- */
- public function setId($value)
- {
- $this->_id = $value;
- }
-
- private $_view;
-
- /**
- * Returns the view object that can be used to render views or view files.
- * The [[render()]] and [[renderFile()]] methods will use
- * this view object to implement the actual view rendering.
- * If not set, it will default to the "view" application component.
- * @return \yii\web\View the view object that can be used to render views or view files.
- */
- public function getView()
- {
- if ($this->_view === null) {
- $this->_view = Yii::$app->getView();
- }
- return $this->_view;
- }
-
- /**
- * Sets the view object to be used by this widget.
- * @param View $view the view object that can be used to render views or view files.
- */
- public function setView($view)
- {
- $this->_view = $view;
- }
-
- /**
- * Executes the widget.
- * @return string the result of widget execution to be outputted.
- */
- public function run()
- {
- }
-
- /**
- * Renders a view.
- * The view to be rendered can be specified in one of the following formats:
- *
- * - path alias (e.g. "@app/views/site/index");
- * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
- * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
- * - absolute path within 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 the currently
- * active module.
- * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
- *
- * 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 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.
- */
- public function render($view, $params = [])
- {
- return $this->getView()->render($view, $params, $this);
- }
-
- /**
- * Renders a view file.
- * @param string $file the view file to be rendered. This can be either a file path or a path alias.
- * @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.
- */
- public function renderFile($file, $params = [])
- {
- return $this->getView()->renderFile($file, $params, $this);
- }
-
- /**
- * Returns the directory containing the view files for this widget.
- * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
- * @return string the directory containing the view files for this widget.
- */
- public function getViewPath()
- {
- $class = new ReflectionClass($this);
- 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;
- }
+ /**
+ * @var integer a counter used to generate [[id]] for widgets.
+ * @internal
+ */
+ public static $counter = 0;
+ /**
+ * @var string the prefix to the automatically generated widget IDs.
+ * @see getId()
+ */
+ public static $autoIdPrefix = 'w';
+
+ /**
+ * @var Widget[] the widgets that are currently being rendered (not ended). This property
+ * is maintained by [[begin()]] and [[end()]] methods.
+ * @internal
+ */
+ public static $stack = [];
+
+ /**
+ * Begins a widget.
+ * This method creates an instance of the calling class. It will apply the configuration
+ * to the created instance. A matching [[end()]] call should be called later.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ * @return static the newly created widget instance
+ */
+ public static function begin($config = [])
+ {
+ $config['class'] = get_called_class();
+ /** @var Widget $widget */
+ $widget = Yii::createObject($config);
+ self::$stack[] = $widget;
+
+ return $widget;
+ }
+
+ /**
+ * Ends a widget.
+ * Note that the rendering result of the widget is directly echoed out.
+ * @return static the widget instance that is ended.
+ * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested
+ */
+ public static function end()
+ {
+ if (!empty(self::$stack)) {
+ $widget = array_pop(self::$stack);
+ if (get_class($widget) === get_called_class()) {
+ $widget->run();
+
+ return $widget;
+ } else {
+ throw new InvalidCallException("Expecting end() of " . get_class($widget) . ", found " . get_called_class());
+ }
+ } else {
+ throw new InvalidCallException("Unexpected " . get_called_class() . '::end() call. A matching begin() is not found.');
+ }
+ }
+
+ /**
+ * Creates a widget instance and runs it.
+ * The widget rendering result is returned by this method.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ * @return string the rendering result of the widget.
+ */
+ public static function widget($config = [])
+ {
+ ob_start();
+ ob_implicit_flush(false);
+ /** @var Widget $widget */
+ $config['class'] = get_called_class();
+ $widget = Yii::createObject($config);
+ $out = $widget->run();
+
+ return ob_get_clean() . $out;
+ }
+
+ private $_id;
+
+ /**
+ * Returns the ID of the widget.
+ * @param boolean $autoGenerate whether to generate an ID if it is not set previously
+ * @return string ID of the widget.
+ */
+ public function getId($autoGenerate = true)
+ {
+ if ($autoGenerate && $this->_id === null) {
+ $this->_id = self::$autoIdPrefix . self::$counter++;
+ }
+
+ return $this->_id;
+ }
+
+ /**
+ * Sets the ID of the widget.
+ * @param string $value id of the widget.
+ */
+ public function setId($value)
+ {
+ $this->_id = $value;
+ }
+
+ private $_view;
+
+ /**
+ * Returns the view object that can be used to render views or view files.
+ * The [[render()]] and [[renderFile()]] methods will use
+ * this view object to implement the actual view rendering.
+ * If not set, it will default to the "view" application component.
+ * @return \yii\web\View the view object that can be used to render views or view files.
+ */
+ public function getView()
+ {
+ if ($this->_view === null) {
+ $this->_view = Yii::$app->getView();
+ }
+
+ return $this->_view;
+ }
+
+ /**
+ * Sets the view object to be used by this widget.
+ * @param View $view the view object that can be used to render views or view files.
+ */
+ public function setView($view)
+ {
+ $this->_view = $view;
+ }
+
+ /**
+ * Executes the widget.
+ * @return string the result of widget execution to be outputted.
+ */
+ public function run()
+ {
+ }
+
+ /**
+ * Renders a view.
+ * The view to be rendered can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
+ * - absolute path within 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 the currently
+ * active module.
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ *
+ * 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 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.
+ */
+ public function render($view, $params = [])
+ {
+ return $this->getView()->render($view, $params, $this);
+ }
+
+ /**
+ * Renders a view file.
+ * @param string $file the view file to be rendered. This can be either a file path or a path alias.
+ * @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.
+ */
+ public function renderFile($file, $params = [])
+ {
+ return $this->getView()->renderFile($file, $params, $this);
+ }
+
+ /**
+ * Returns the directory containing the view files for this widget.
+ * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
+ * @return string the directory containing the view files for this widget.
+ */
+ public function getViewPath()
+ {
+ $class = new ReflectionClass($this);
+
+ 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;
+ }
}
diff --git a/framework/behaviors/AttributeBehavior.php b/framework/behaviors/AttributeBehavior.php
index 4f6430f2f00..eaf23200310 100644
--- a/framework/behaviors/AttributeBehavior.php
+++ b/framework/behaviors/AttributeBehavior.php
@@ -46,66 +46,65 @@
*/
class AttributeBehavior extends Behavior
{
- /**
- * @var array list of attributes that are to be automatically filled with the value specified via [[value]].
- * The array keys are the ActiveRecord events upon which the attributes are to be updated,
- * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
- * a single attribute, or an array to represent a list of attributes. For example,
- *
- * ```php
- * [
- * ActiveRecord::EVENT_BEFORE_INSERT => ['attribute1', 'attribute2'],
- * ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
- * ]
- * ```
- */
- public $attributes = [];
- /**
- * @var mixed the value that will be assigned to the current attributes. This can be an anonymous function
- * or an arbitrary value. If the former, the return value of the function will be assigned to the attributes.
- * The signature of the function should be as follows,
- *
- * ```php
- * function ($event) {
- * // return value will be assigned to the attribute
- * }
- * ```
- */
- public $value;
+ /**
+ * @var array list of attributes that are to be automatically filled with the value specified via [[value]].
+ * The array keys are the ActiveRecord events upon which the attributes are to be updated,
+ * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
+ * a single attribute, or an array to represent a list of attributes. For example,
+ *
+ * ```php
+ * [
+ * ActiveRecord::EVENT_BEFORE_INSERT => ['attribute1', 'attribute2'],
+ * ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
+ * ]
+ * ```
+ */
+ public $attributes = [];
+ /**
+ * @var mixed the value that will be assigned to the current attributes. This can be an anonymous function
+ * or an arbitrary value. If the former, the return value of the function will be assigned to the attributes.
+ * The signature of the function should be as follows,
+ *
+ * ```php
+ * function ($event) {
+ * // return value will be assigned to the attribute
+ * }
+ * ```
+ */
+ public $value;
+ /**
+ * @inheritdoc
+ */
+ public function events()
+ {
+ return array_fill_keys(array_keys($this->attributes), 'evaluateAttributes');
+ }
- /**
- * @inheritdoc
- */
- public function events()
- {
- return array_fill_keys(array_keys($this->attributes), 'evaluateAttributes');
- }
+ /**
+ * Evaluates the attribute value and assigns it to the current attributes.
+ * @param $event
+ */
+ public function evaluateAttributes($event)
+ {
+ if (!empty($this->attributes[$event->name])) {
+ $attributes = (array) $this->attributes[$event->name];
+ $value = $this->getValue($event);
+ foreach ($attributes as $attribute) {
+ $this->owner->$attribute = $value;
+ }
+ }
+ }
- /**
- * Evaluates the attribute value and assigns it to the current attributes.
- * @param $event
- */
- public function evaluateAttributes($event)
- {
- if (!empty($this->attributes[$event->name])) {
- $attributes = (array)$this->attributes[$event->name];
- $value = $this->getValue($event);
- foreach ($attributes as $attribute) {
- $this->owner->$attribute = $value;
- }
- }
- }
-
- /**
- * Returns the value of the current attributes.
- * This method is called by [[evaluateAttributes()]]. Its return value will be assigned
- * to the attributes corresponding to the triggering event.
- * @param Event $event the event that triggers the current attribute updating.
- * @return mixed the attribute value
- */
- protected function getValue($event)
- {
- return $this->value instanceof Closure ? call_user_func($this->value, $event) : $this->value;
- }
+ /**
+ * Returns the value of the current attributes.
+ * This method is called by [[evaluateAttributes()]]. Its return value will be assigned
+ * to the attributes corresponding to the triggering event.
+ * @param Event $event the event that triggers the current attribute updating.
+ * @return mixed the attribute value
+ */
+ protected function getValue($event)
+ {
+ return $this->value instanceof Closure ? call_user_func($this->value, $event) : $this->value;
+ }
}
diff --git a/framework/behaviors/BlameableBehavior.php b/framework/behaviors/BlameableBehavior.php
index 0a9dba9ccab..8584d9d4c1c 100644
--- a/framework/behaviors/BlameableBehavior.php
+++ b/framework/behaviors/BlameableBehavior.php
@@ -53,46 +53,47 @@
*/
class BlameableBehavior extends AttributeBehavior
{
- /**
- * @var array list of attributes that are to be automatically filled with the current user ID.
- * The array keys are the ActiveRecord events upon which the attributes are to be filled with the user ID,
- * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
- * a single attribute, or an array to represent a list of attributes.
- * The default setting is to update both of the `created_by` and `updated_by` attributes upon AR insertion,
- * and update the `updated_by` attribute upon AR updating.
- */
- public $attributes = [
- BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_by', 'updated_by'],
- BaseActiveRecord::EVENT_BEFORE_UPDATE => 'updated_by',
- ];
- /**
- * @var callable the value that will be assigned to the attributes. This should be a valid
- * PHP callable whose return value will be assigned to the current attribute(s).
- * The signature of the callable should be:
- *
- * ```php
- * function ($event) {
- * // return value will be assigned to the attribute(s)
- * }
- * ```
- *
- * If this property is not set, the value of `Yii::$app->user->id` will be assigned to the attribute(s).
- */
- public $value;
+ /**
+ * @var array list of attributes that are to be automatically filled with the current user ID.
+ * The array keys are the ActiveRecord events upon which the attributes are to be filled with the user ID,
+ * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
+ * a single attribute, or an array to represent a list of attributes.
+ * The default setting is to update both of the `created_by` and `updated_by` attributes upon AR insertion,
+ * and update the `updated_by` attribute upon AR updating.
+ */
+ public $attributes = [
+ BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_by', 'updated_by'],
+ BaseActiveRecord::EVENT_BEFORE_UPDATE => 'updated_by',
+ ];
+ /**
+ * @var callable the value that will be assigned to the attributes. This should be a valid
+ * PHP callable whose return value will be assigned to the current attribute(s).
+ * The signature of the callable should be:
+ *
+ * ```php
+ * function ($event) {
+ * // return value will be assigned to the attribute(s)
+ * }
+ * ```
+ *
+ * If this property is not set, the value of `Yii::$app->user->id` will be assigned to the attribute(s).
+ */
+ public $value;
- /**
- * Evaluates the value of the user.
- * The return result of this method will be assigned to the current attribute(s).
- * @param Event $event
- * @return mixed the value of the user.
- */
- protected function getValue($event)
- {
- if ($this->value === null) {
- $user = Yii::$app->getUser();
- return $user && !$user->isGuest ? $user->id : null;
- } else {
- return call_user_func($this->value, $event);
- }
- }
+ /**
+ * Evaluates the value of the user.
+ * The return result of this method will be assigned to the current attribute(s).
+ * @param Event $event
+ * @return mixed the value of the user.
+ */
+ protected function getValue($event)
+ {
+ if ($this->value === null) {
+ $user = Yii::$app->getUser();
+
+ return $user && !$user->isGuest ? $user->id : null;
+ } else {
+ return call_user_func($this->value, $event);
+ }
+ }
}
diff --git a/framework/behaviors/TimestampBehavior.php b/framework/behaviors/TimestampBehavior.php
index 131348875de..7b5615f03bd 100644
--- a/framework/behaviors/TimestampBehavior.php
+++ b/framework/behaviors/TimestampBehavior.php
@@ -63,49 +63,48 @@
*/
class TimestampBehavior extends AttributeBehavior
{
- /**
- * @var array list of attributes that are to be automatically filled with timestamps.
- * The array keys are the ActiveRecord events upon which the attributes are to be filled with timestamps,
- * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
- * a single attribute, or an array to represent a list of attributes.
- * The default setting is to update both of the `created_at` and `updated_at` attributes upon AR insertion,
- * and update the `updated_at` attribute upon AR updating.
- */
- public $attributes = [
- BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
- BaseActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at',
- ];
- /**
- * @var callable|Expression The expression that will be used for generating the timestamp.
- * This can be either an anonymous function that returns the timestamp value,
- * or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`).
- * If not set, it will use the value of `time()` to set the attributes.
- */
- public $value;
+ /**
+ * @var array list of attributes that are to be automatically filled with timestamps.
+ * The array keys are the ActiveRecord events upon which the attributes are to be filled with timestamps,
+ * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
+ * a single attribute, or an array to represent a list of attributes.
+ * The default setting is to update both of the `created_at` and `updated_at` attributes upon AR insertion,
+ * and update the `updated_at` attribute upon AR updating.
+ */
+ public $attributes = [
+ BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
+ BaseActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at',
+ ];
+ /**
+ * @var callable|Expression The expression that will be used for generating the timestamp.
+ * This can be either an anonymous function that returns the timestamp value,
+ * or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`).
+ * If not set, it will use the value of `time()` to set the attributes.
+ */
+ public $value;
+ /**
+ * @inheritdoc
+ */
+ protected function getValue($event)
+ {
+ if ($this->value instanceof Expression) {
+ return $this->value;
+ } else {
+ return $this->value !== null ? call_user_func($this->value, $event) : time();
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function getValue($event)
- {
- if ($this->value instanceof Expression) {
- return $this->value;
- } else {
- return $this->value !== null ? call_user_func($this->value, $event) : time();
- }
- }
-
- /**
- * Updates a timestamp attribute to the current timestamp.
- *
- * ```php
- * $model->touch('lastVisit');
- * ```
- * @param string $attribute the name of the attribute to update.
- */
- public function touch($attribute)
- {
- $this->owner->updateAttributes(array_fill_keys((array)$attribute, $this->getValue(null)));
- }
+ /**
+ * Updates a timestamp attribute to the current timestamp.
+ *
+ * ```php
+ * $model->touch('lastVisit');
+ * ```
+ * @param string $attribute the name of the attribute to update.
+ */
+ public function touch($attribute)
+ {
+ $this->owner->updateAttributes(array_fill_keys((array) $attribute, $this->getValue(null)));
+ }
}
diff --git a/framework/caching/ApcCache.php b/framework/caching/ApcCache.php
index 8a0d20763ae..db69f5be09d 100644
--- a/framework/caching/ApcCache.php
+++ b/framework/caching/ApcCache.php
@@ -20,114 +20,115 @@
*/
class ApcCache extends Cache
{
- /**
- * Checks whether a specified key exists in the cache.
- * This can be faster than getting the value from the cache if the data is big.
- * Note that this method does not check whether the dependency associated
- * with the cached data, if there is any, has changed. So a call to [[get]]
- * may return false while exists returns true.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
- */
- public function exists($key)
- {
- $key = $this->buildKey($key);
- return apc_exists($key);
- }
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- return apc_fetch($key);
- }
+ return apc_exists($key);
+ }
- /**
- * Retrieves multiple values from cache with the specified keys.
- * @param array $keys a list of keys identifying the cached values
- * @return array a list of cached values indexed by the keys
- */
- protected function getValues($keys)
- {
- return apc_fetch($keys);
- }
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return apc_fetch($key);
+ }
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- return apc_store($key, $value, $expire);
- }
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ return apc_fetch($keys);
+ }
- /**
- * Stores multiple key-value pairs in cache.
- * @param array $data array where key corresponds to cache key while value
- * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
- * @return array array of failed keys
- */
- protected function setValues($data, $expire)
- {
- return array_keys(apc_store($data, null, $expire));
- }
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return apc_store($key, $value, $expire);
+ }
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- return apc_add($key, $value, $expire);
- }
+ /**
+ * Stores multiple key-value pairs in cache.
+ * @param array $data array where key corresponds to cache key while value
+ * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
+ * @return array array of failed keys
+ */
+ protected function setValues($data, $expire)
+ {
+ return array_keys(apc_store($data, null, $expire));
+ }
- /**
- * Adds multiple key-value pairs to cache.
- * @param array $data array where key corresponds to cache key while value is the value stored
- * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
- * @return array array of failed keys
- */
- protected function addValues($data, $expire)
- {
- return array_keys(apc_add($data, null, $expire));
- }
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return apc_add($key, $value, $expire);
+ }
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- return apc_delete($key);
- }
+ /**
+ * Adds multiple key-value pairs to cache.
+ * @param array $data array where key corresponds to cache key while value is the value stored
+ * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
+ * @return array array of failed keys
+ */
+ protected function addValues($data, $expire)
+ {
+ return array_keys(apc_add($data, null, $expire));
+ }
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- if (extension_loaded('apcu')) {
- return apc_clear_cache();
- } else {
- return apc_clear_cache('user');
- }
- }
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return apc_delete($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ if (extension_loaded('apcu')) {
+ return apc_clear_cache();
+ } else {
+ return apc_clear_cache('user');
+ }
+ }
}
diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php
index 85d294edadc..e6e7083e12f 100644
--- a/framework/caching/Cache.php
+++ b/framework/caching/Cache.php
@@ -51,421 +51,431 @@
*/
abstract class Cache extends Component implements \ArrayAccess
{
- /**
- * @var string a string prefixed to every cache key so that it is unique. If not set,
- * it will use a prefix generated from [[\yii\base\Application::id]]. You may set this property to be an empty string
- * if you don't want to use key prefix. It is recommended that you explicitly set this property to some
- * static value if the cached data needs to be shared among multiple applications.
- *
- * To ensure interoperability, only alphanumeric characters should be used.
- */
- public $keyPrefix;
- /**
- * @var array|boolean the functions used to serialize and unserialize cached data. Defaults to null, meaning
- * using the default PHP `serialize()` and `unserialize()` functions. If you want to use some more efficient
- * serializer (e.g. [igbinary](http://pecl.php.net/package/igbinary)), you may configure this property with
- * a two-element array. The first element specifies the serialization function, and the second the deserialization
- * function. If this property is set false, data will be directly sent to and retrieved from the underlying
- * cache component without any serialization or deserialization. You should not turn off serialization if
- * you are using [[Dependency|cache dependency]], because it relies on data serialization.
- */
- public $serializer;
-
-
- /**
- * Initializes the application component.
- * This method overrides the parent implementation by setting default cache key prefix.
- */
- public function init()
- {
- parent::init();
- if ($this->keyPrefix === null) {
- $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
- }
- }
-
- /**
- * Builds a normalized cache key from a given key.
- *
- * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
- * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key
- * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]].
- *
- * @param mixed $key the key to be normalized
- * @return string the generated cache key
- */
- protected function buildKey($key)
- {
- if (is_string($key)) {
- $key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
- } else {
- $key = md5(json_encode($key));
- }
- return $this->keyPrefix . $key;
- }
-
- /**
- * Retrieves a value from cache with a specified key.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return mixed the value stored in cache, false if the value is not in the cache, expired,
- * or the dependency associated with the cached data has changed.
- */
- public function get($key)
- {
- $key = $this->buildKey($key);
- $value = $this->getValue($key);
- if ($value === false || $this->serializer === false) {
- return $value;
- } elseif ($this->serializer === null) {
- $value = unserialize($value);
- } else {
- $value = call_user_func($this->serializer[1], $value);
- }
- if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
- return $value[0];
- } else {
- return false;
- }
- }
-
- /**
- * Checks whether a specified key exists in the cache.
- * This can be faster than getting the value from the cache if the data is big.
- * In case a cache does not support this feature natively, this method will try to simulate it
- * but has no performance improvement over getting it.
- * Note that this method does not check whether the dependency associated
- * with the cached data, if there is any, has changed. So a call to [[get]]
- * may return false while exists returns true.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
- */
- public function exists($key)
- {
- $key = $this->buildKey($key);
- $value = $this->getValue($key);
- return $value !== false;
- }
-
- /**
- * Retrieves multiple values from cache with the specified keys.
- * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time,
- * which may improve the performance. In case a cache does not support this feature natively,
- * this method will try to simulate it.
- * @param array $keys list of keys identifying the cached values
- * @return array list of cached values corresponding to the specified keys. The array
- * is returned in terms of (key, value) pairs.
- * If a value is not cached or expired, the corresponding array value will be false.
- */
- public function mget($keys)
- {
- $keyMap = [];
- foreach ($keys as $key) {
- $keyMap[$key] = $this->buildKey($key);
- }
- $values = $this->getValues(array_values($keyMap));
- $results = [];
- foreach ($keyMap as $key => $newKey) {
- $results[$key] = false;
- if (isset($values[$newKey])) {
- if ($this->serializer === false) {
- $results[$key] = $values[$newKey];
- } else {
- $value = $this->serializer === null ? unserialize($values[$newKey])
- : call_user_func($this->serializer[1], $values[$newKey]);
-
- if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
- $results[$key] = $value[0];
- }
- }
- }
- }
- return $results;
- }
-
- /**
- * Stores a value identified by a key into cache.
- * If the cache already contains such a key, the existing value and
- * expiration time will be replaced with the new ones, respectively.
- *
- * @param mixed $key a key identifying the value to be cached. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @param mixed $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @param Dependency $dependency dependency of the cached item. If the dependency changes,
- * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
- * This parameter is ignored if [[serializer]] is false.
- * @return boolean whether the value is successfully stored into cache
- */
- public function set($key, $value, $expire = 0, $dependency = null)
- {
- if ($dependency !== null && $this->serializer !== false) {
- $dependency->evaluateDependency($this);
- }
- if ($this->serializer === null) {
- $value = serialize([$value, $dependency]);
- } elseif ($this->serializer !== false) {
- $value = call_user_func($this->serializer[0], [$value, $dependency]);
- }
- $key = $this->buildKey($key);
- return $this->setValue($key, $value, $expire);
- }
-
- /**
- * Stores multiple items in cache. Each item contains a value identified by a key.
- * If the cache already contains such a key, the existing value and
- * expiration time will be replaced with the new ones, respectively.
- *
- * @param array $items the items to be cached, as key-value pairs.
- * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire.
- * @param Dependency $dependency dependency of the cached items. If the dependency changes,
- * the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
- * This parameter is ignored if [[serializer]] is false.
- * @return boolean whether the items are successfully stored into cache
- */
- public function mset($items, $expire = 0, $dependency = null)
- {
- if ($dependency !== null && $this->serializer !== false) {
- $dependency->evaluateDependency($this);
- }
-
- $data = [];
- foreach ($items as $key => $value) {
- if ($this->serializer === null) {
- $value = serialize([$value, $dependency]);
- } elseif ($this->serializer !== false) {
- $value = call_user_func($this->serializer[0], [$value, $dependency]);
- }
-
- $key = $this->buildKey($key);
- $data[$key] = $value;
- }
- return $this->setValues($data, $expire);
- }
-
- /**
- * Stores multiple items in cache. Each item contains a value identified by a key.
- * If the cache already contains such a key, the existing value and expiration time will be preserved.
- *
- * @param array $items the items to be cached, as key-value pairs.
- * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire.
- * @param Dependency $dependency dependency of the cached items. If the dependency changes,
- * the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
- * This parameter is ignored if [[serializer]] is false.
- * @return boolean whether the items are successfully stored into cache
- */
- public function madd($items, $expire = 0, $dependency = null)
- {
- if ($dependency !== null && $this->serializer !== false) {
- $dependency->evaluateDependency($this);
- }
-
- $data = [];
- foreach ($items as $key => $value) {
- if ($this->serializer === null) {
- $value = serialize([$value, $dependency]);
- } elseif ($this->serializer !== false) {
- $value = call_user_func($this->serializer[0], [$value, $dependency]);
- }
-
- $key = $this->buildKey($key);
- $data[$key] = $value;
- }
- return $this->addValues($data, $expire);
- }
-
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * Nothing will be done if the cache already contains the key.
- * @param mixed $key a key identifying the value to be cached. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @param mixed $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @param Dependency $dependency dependency of the cached item. If the dependency changes,
- * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
- * This parameter is ignored if [[serializer]] is false.
- * @return boolean whether the value is successfully stored into cache
- */
- public function add($key, $value, $expire = 0, $dependency = null)
- {
- if ($dependency !== null && $this->serializer !== false) {
- $dependency->evaluateDependency($this);
- }
- if ($this->serializer === null) {
- $value = serialize([$value, $dependency]);
- } elseif ($this->serializer !== false) {
- $value = call_user_func($this->serializer[0], [$value, $dependency]);
- }
- $key = $this->buildKey($key);
- return $this->addValue($key, $value, $expire);
- }
-
- /**
- * Deletes a value with the specified key from cache
- * @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean if no error happens during deletion
- */
- public function delete($key)
- {
- $key = $this->buildKey($key);
- return $this->deleteValue($key);
- }
-
- /**
- * Deletes all values from cache.
- * Be careful of performing this operation if the cache is shared among multiple applications.
- * @return boolean whether the flush operation was successful.
- */
- public function flush()
- {
- return $this->flushValues();
- }
-
- /**
- * Retrieves a value from cache with a specified key.
- * This method should be implemented by child classes to retrieve the data
- * from specific cache storage.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- abstract protected function getValue($key);
-
- /**
- * Stores a value identified by a key in cache.
- * This method should be implemented by child classes to store the data
- * in specific cache storage.
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- abstract protected function setValue($key, $value, $expire);
-
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This method should be implemented by child classes to store the data
- * in specific cache storage.
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- abstract protected function addValue($key, $value, $expire);
-
- /**
- * Deletes a value with the specified key from cache
- * This method should be implemented by child classes to delete the data from actual cache storage.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- abstract protected function deleteValue($key);
-
- /**
- * Deletes all values from cache.
- * Child classes may implement this method to realize the flush operation.
- * @return boolean whether the flush operation was successful.
- */
- abstract protected function flushValues();
-
- /**
- * Retrieves multiple values from cache with the specified keys.
- * The default implementation calls [[getValue()]] multiple times to retrieve
- * the cached values one by one. If the underlying cache storage supports multiget,
- * this method should be overridden to exploit that feature.
- * @param array $keys a list of keys identifying the cached values
- * @return array a list of cached values indexed by the keys
- */
- protected function getValues($keys)
- {
- $results = [];
- foreach ($keys as $key) {
- $results[$key] = $this->getValue($key);
- }
- return $results;
- }
-
- /**
- * Stores multiple key-value pairs in cache.
- * The default implementation calls [[setValue()]] multiple times store values one by one. If the underlying cache
- * storage supports multi-set, this method should be overridden to exploit that feature.
- * @param array $data array where key corresponds to cache key while value is the value stored
- * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
- * @return array array of failed keys
- */
- protected function setValues($data, $expire)
- {
- $failedKeys = [];
- foreach ($data as $key => $value) {
- if ($this->setValue($key, $value, $expire) === false) {
- $failedKeys[] = $key;
- }
- }
- return $failedKeys;
- }
-
- /**
- * Adds multiple key-value pairs to cache.
- * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache
- * storage supports multi-add, this method should be overridden to exploit that feature.
- * @param array $data array where key corresponds to cache key while value is the value stored
- * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
- * @return array array of failed keys
- */
- protected function addValues($data, $expire)
- {
- $failedKeys = [];
- foreach ($data as $key => $value) {
- if ($this->addValue($key, $value, $expire) === false) {
- $failedKeys[] = $key;
- }
- }
- return $failedKeys;
- }
-
- /**
- * Returns whether there is a cache entry with a specified key.
- * This method is required by the interface ArrayAccess.
- * @param string $key a key identifying the cached value
- * @return boolean
- */
- public function offsetExists($key)
- {
- return $this->get($key) !== false;
- }
-
- /**
- * Retrieves the value from cache with a specified key.
- * This method is required by the interface ArrayAccess.
- * @param string $key a key identifying the cached value
- * @return mixed the value stored in cache, false if the value is not in the cache or expired.
- */
- public function offsetGet($key)
- {
- return $this->get($key);
- }
-
- /**
- * Stores the value identified by a key into cache.
- * If the cache already contains such a key, the existing value will be
- * replaced with the new ones. To add expiration and dependencies, use the [[set()]] method.
- * This method is required by the interface ArrayAccess.
- * @param string $key the key identifying the value to be cached
- * @param mixed $value the value to be cached
- */
- public function offsetSet($key, $value)
- {
- $this->set($key, $value);
- }
-
- /**
- * Deletes the value with the specified key from cache
- * This method is required by the interface ArrayAccess.
- * @param string $key the key of the value to be deleted
- */
- public function offsetUnset($key)
- {
- $this->delete($key);
- }
+ /**
+ * @var string a string prefixed to every cache key so that it is unique. If not set,
+ * it will use a prefix generated from [[\yii\base\Application::id]]. You may set this property to be an empty string
+ * if you don't want to use key prefix. It is recommended that you explicitly set this property to some
+ * static value if the cached data needs to be shared among multiple applications.
+ *
+ * To ensure interoperability, only alphanumeric characters should be used.
+ */
+ public $keyPrefix;
+ /**
+ * @var array|boolean the functions used to serialize and unserialize cached data. Defaults to null, meaning
+ * using the default PHP `serialize()` and `unserialize()` functions. If you want to use some more efficient
+ * serializer (e.g. [igbinary](http://pecl.php.net/package/igbinary)), you may configure this property with
+ * a two-element array. The first element specifies the serialization function, and the second the deserialization
+ * function. If this property is set false, data will be directly sent to and retrieved from the underlying
+ * cache component without any serialization or deserialization. You should not turn off serialization if
+ * you are using [[Dependency|cache dependency]], because it relies on data serialization.
+ */
+ public $serializer;
+
+ /**
+ * Initializes the application component.
+ * This method overrides the parent implementation by setting default cache key prefix.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->keyPrefix === null) {
+ $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
+ }
+ }
+
+ /**
+ * Builds a normalized cache key from a given key.
+ *
+ * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
+ * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key
+ * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]].
+ *
+ * @param mixed $key the key to be normalized
+ * @return string the generated cache key
+ */
+ protected function buildKey($key)
+ {
+ if (is_string($key)) {
+ $key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
+ } else {
+ $key = md5(json_encode($key));
+ }
+
+ return $this->keyPrefix . $key;
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return mixed the value stored in cache, false if the value is not in the cache, expired,
+ * or the dependency associated with the cached data has changed.
+ */
+ public function get($key)
+ {
+ $key = $this->buildKey($key);
+ $value = $this->getValue($key);
+ if ($value === false || $this->serializer === false) {
+ return $value;
+ } elseif ($this->serializer === null) {
+ $value = unserialize($value);
+ } else {
+ $value = call_user_func($this->serializer[1], $value);
+ }
+ if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
+ return $value[0];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * In case a cache does not support this feature natively, this method will try to simulate it
+ * but has no performance improvement over getting it.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
+ $value = $this->getValue($key);
+
+ return $value !== false;
+ }
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time,
+ * which may improve the performance. In case a cache does not support this feature natively,
+ * this method will try to simulate it.
+ * @param array $keys list of keys identifying the cached values
+ * @return array list of cached values corresponding to the specified keys. The array
+ * is returned in terms of (key, value) pairs.
+ * If a value is not cached or expired, the corresponding array value will be false.
+ */
+ public function mget($keys)
+ {
+ $keyMap = [];
+ foreach ($keys as $key) {
+ $keyMap[$key] = $this->buildKey($key);
+ }
+ $values = $this->getValues(array_values($keyMap));
+ $results = [];
+ foreach ($keyMap as $key => $newKey) {
+ $results[$key] = false;
+ if (isset($values[$newKey])) {
+ if ($this->serializer === false) {
+ $results[$key] = $values[$newKey];
+ } else {
+ $value = $this->serializer === null ? unserialize($values[$newKey])
+ : call_user_func($this->serializer[1], $values[$newKey]);
+
+ if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
+ $results[$key] = $value[0];
+ }
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Stores a value identified by a key into cache.
+ * If the cache already contains such a key, the existing value and
+ * expiration time will be replaced with the new ones, respectively.
+ *
+ * @param mixed $key a key identifying the value to be cached. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @param mixed $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @param Dependency $dependency dependency of the cached item. If the dependency changes,
+ * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
+ * This parameter is ignored if [[serializer]] is false.
+ * @return boolean whether the value is successfully stored into cache
+ */
+ public function set($key, $value, $expire = 0, $dependency = null)
+ {
+ if ($dependency !== null && $this->serializer !== false) {
+ $dependency->evaluateDependency($this);
+ }
+ if ($this->serializer === null) {
+ $value = serialize([$value, $dependency]);
+ } elseif ($this->serializer !== false) {
+ $value = call_user_func($this->serializer[0], [$value, $dependency]);
+ }
+ $key = $this->buildKey($key);
+
+ return $this->setValue($key, $value, $expire);
+ }
+
+ /**
+ * Stores multiple items in cache. Each item contains a value identified by a key.
+ * If the cache already contains such a key, the existing value and
+ * expiration time will be replaced with the new ones, respectively.
+ *
+ * @param array $items the items to be cached, as key-value pairs.
+ * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire.
+ * @param Dependency $dependency dependency of the cached items. If the dependency changes,
+ * the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
+ * This parameter is ignored if [[serializer]] is false.
+ * @return boolean whether the items are successfully stored into cache
+ */
+ public function mset($items, $expire = 0, $dependency = null)
+ {
+ if ($dependency !== null && $this->serializer !== false) {
+ $dependency->evaluateDependency($this);
+ }
+
+ $data = [];
+ foreach ($items as $key => $value) {
+ if ($this->serializer === null) {
+ $value = serialize([$value, $dependency]);
+ } elseif ($this->serializer !== false) {
+ $value = call_user_func($this->serializer[0], [$value, $dependency]);
+ }
+
+ $key = $this->buildKey($key);
+ $data[$key] = $value;
+ }
+
+ return $this->setValues($data, $expire);
+ }
+
+ /**
+ * Stores multiple items in cache. Each item contains a value identified by a key.
+ * If the cache already contains such a key, the existing value and expiration time will be preserved.
+ *
+ * @param array $items the items to be cached, as key-value pairs.
+ * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire.
+ * @param Dependency $dependency dependency of the cached items. If the dependency changes,
+ * the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
+ * This parameter is ignored if [[serializer]] is false.
+ * @return boolean whether the items are successfully stored into cache
+ */
+ public function madd($items, $expire = 0, $dependency = null)
+ {
+ if ($dependency !== null && $this->serializer !== false) {
+ $dependency->evaluateDependency($this);
+ }
+
+ $data = [];
+ foreach ($items as $key => $value) {
+ if ($this->serializer === null) {
+ $value = serialize([$value, $dependency]);
+ } elseif ($this->serializer !== false) {
+ $value = call_user_func($this->serializer[0], [$value, $dependency]);
+ }
+
+ $key = $this->buildKey($key);
+ $data[$key] = $value;
+ }
+
+ return $this->addValues($data, $expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * Nothing will be done if the cache already contains the key.
+ * @param mixed $key a key identifying the value to be cached. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @param mixed $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @param Dependency $dependency dependency of the cached item. If the dependency changes,
+ * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
+ * This parameter is ignored if [[serializer]] is false.
+ * @return boolean whether the value is successfully stored into cache
+ */
+ public function add($key, $value, $expire = 0, $dependency = null)
+ {
+ if ($dependency !== null && $this->serializer !== false) {
+ $dependency->evaluateDependency($this);
+ }
+ if ($this->serializer === null) {
+ $value = serialize([$value, $dependency]);
+ } elseif ($this->serializer !== false) {
+ $value = call_user_func($this->serializer[0], [$value, $dependency]);
+ }
+ $key = $this->buildKey($key);
+
+ return $this->addValue($key, $value, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean if no error happens during deletion
+ */
+ public function delete($key)
+ {
+ $key = $this->buildKey($key);
+
+ return $this->deleteValue($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * Be careful of performing this operation if the cache is shared among multiple applications.
+ * @return boolean whether the flush operation was successful.
+ */
+ public function flush()
+ {
+ return $this->flushValues();
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This method should be implemented by child classes to retrieve the data
+ * from specific cache storage.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ abstract protected function getValue($key);
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ abstract protected function setValue($key, $value, $expire);
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ abstract protected function addValue($key, $value, $expire);
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This method should be implemented by child classes to delete the data from actual cache storage.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ abstract protected function deleteValue($key);
+
+ /**
+ * Deletes all values from cache.
+ * Child classes may implement this method to realize the flush operation.
+ * @return boolean whether the flush operation was successful.
+ */
+ abstract protected function flushValues();
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * The default implementation calls [[getValue()]] multiple times to retrieve
+ * the cached values one by one. If the underlying cache storage supports multiget,
+ * this method should be overridden to exploit that feature.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ $results = [];
+ foreach ($keys as $key) {
+ $results[$key] = $this->getValue($key);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Stores multiple key-value pairs in cache.
+ * The default implementation calls [[setValue()]] multiple times store values one by one. If the underlying cache
+ * storage supports multi-set, this method should be overridden to exploit that feature.
+ * @param array $data array where key corresponds to cache key while value is the value stored
+ * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
+ * @return array array of failed keys
+ */
+ protected function setValues($data, $expire)
+ {
+ $failedKeys = [];
+ foreach ($data as $key => $value) {
+ if ($this->setValue($key, $value, $expire) === false) {
+ $failedKeys[] = $key;
+ }
+ }
+
+ return $failedKeys;
+ }
+
+ /**
+ * Adds multiple key-value pairs to cache.
+ * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache
+ * storage supports multi-add, this method should be overridden to exploit that feature.
+ * @param array $data array where key corresponds to cache key while value is the value stored
+ * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
+ * @return array array of failed keys
+ */
+ protected function addValues($data, $expire)
+ {
+ $failedKeys = [];
+ foreach ($data as $key => $value) {
+ if ($this->addValue($key, $value, $expire) === false) {
+ $failedKeys[] = $key;
+ }
+ }
+
+ return $failedKeys;
+ }
+
+ /**
+ * Returns whether there is a cache entry with a specified key.
+ * This method is required by the interface ArrayAccess.
+ * @param string $key a key identifying the cached value
+ * @return boolean
+ */
+ public function offsetExists($key)
+ {
+ return $this->get($key) !== false;
+ }
+
+ /**
+ * Retrieves the value from cache with a specified key.
+ * This method is required by the interface ArrayAccess.
+ * @param string $key a key identifying the cached value
+ * @return mixed the value stored in cache, false if the value is not in the cache or expired.
+ */
+ public function offsetGet($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Stores the value identified by a key into cache.
+ * If the cache already contains such a key, the existing value will be
+ * replaced with the new ones. To add expiration and dependencies, use the [[set()]] method.
+ * This method is required by the interface ArrayAccess.
+ * @param string $key the key identifying the value to be cached
+ * @param mixed $value the value to be cached
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Deletes the value with the specified key from cache
+ * This method is required by the interface ArrayAccess.
+ * @param string $key the key of the value to be deleted
+ */
+ public function offsetUnset($key)
+ {
+ $this->delete($key);
+ }
}
diff --git a/framework/caching/ChainedDependency.php b/framework/caching/ChainedDependency.php
index f8bfee3c21e..46e014e0ac1 100644
--- a/framework/caching/ChainedDependency.php
+++ b/framework/caching/ChainedDependency.php
@@ -19,57 +19,58 @@
*/
class ChainedDependency extends Dependency
{
- /**
- * @var Dependency[] list of dependencies that this dependency is composed of.
- * Each array element must be a dependency object.
- */
- public $dependencies = [];
- /**
- * @var boolean whether this dependency is depending on every dependency in [[dependencies]].
- * Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed.
- * When it is set false, it means if one of the dependencies has NOT changed, this dependency
- * is considered NOT changed.
- */
- public $dependOnAll = true;
+ /**
+ * @var Dependency[] list of dependencies that this dependency is composed of.
+ * Each array element must be a dependency object.
+ */
+ public $dependencies = [];
+ /**
+ * @var boolean whether this dependency is depending on every dependency in [[dependencies]].
+ * Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed.
+ * When it is set false, it means if one of the dependencies has NOT changed, this dependency
+ * is considered NOT changed.
+ */
+ public $dependOnAll = true;
- /**
- * Evaluates the dependency by generating and saving the data related with dependency.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- */
- public function evaluateDependency($cache)
- {
- foreach ($this->dependencies as $dependency) {
- $dependency->evaluateDependency($cache);
- }
- }
+ /**
+ * Evaluates the dependency by generating and saving the data related with dependency.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ */
+ public function evaluateDependency($cache)
+ {
+ foreach ($this->dependencies as $dependency) {
+ $dependency->evaluateDependency($cache);
+ }
+ }
- /**
- * Generates the data needed to determine if dependency has been changed.
- * This method does nothing in this class.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return mixed the data needed to determine if dependency has been changed.
- */
- protected function generateDependencyData($cache)
- {
- return null;
- }
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method does nothing in this class.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ protected function generateDependencyData($cache)
+ {
+ return null;
+ }
- /**
- * Performs the actual dependency checking.
- * This method returns true if any of the dependency objects
- * reports a dependency change.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return boolean whether the dependency is changed or not.
- */
- public function getHasChanged($cache)
- {
- foreach ($this->dependencies as $dependency) {
- if ($this->dependOnAll && $dependency->getHasChanged($cache)) {
- return true;
- } elseif (!$this->dependOnAll && !$dependency->getHasChanged($cache)) {
- return false;
- }
- }
- return !$this->dependOnAll;
- }
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if any of the dependency objects
+ * reports a dependency change.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged($cache)
+ {
+ foreach ($this->dependencies as $dependency) {
+ if ($this->dependOnAll && $dependency->getHasChanged($cache)) {
+ return true;
+ } elseif (!$this->dependOnAll && !$dependency->getHasChanged($cache)) {
+ return false;
+ }
+ }
+
+ return !$this->dependOnAll;
+ }
}
diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php
index 76e2341792f..0731b1eddaa 100644
--- a/framework/caching/DbCache.php
+++ b/framework/caching/DbCache.php
@@ -35,240 +35,246 @@
*/
class DbCache extends Cache
{
- /**
- * @var Connection|string the DB connection object or the application component ID of the DB connection.
- * After the DbCache object is created, if you want to change this property, you should only assign it
- * with a DB connection object.
- */
- public $db = 'db';
- /**
- * @var string name of the DB table to store cache content.
- * The table should be pre-created as follows:
- *
- * ~~~
- * CREATE TABLE tbl_cache (
- * id char(128) NOT NULL PRIMARY KEY,
- * expire int(11),
- * data BLOB
- * );
- * ~~~
- *
- * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
- * that can be used for some popular DBMS:
- *
- * - MySQL: LONGBLOB
- * - PostgreSQL: BYTEA
- * - MSSQL: BLOB
- *
- * When using DbCache in a production server, we recommend you create a DB index for the 'expire'
- * column in the cache table to improve the performance.
- */
- public $cacheTable = '{{%cache}}';
- /**
- * @var integer the probability (parts per million) that garbage collection (GC) should be performed
- * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
- * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
- **/
- public $gcProbability = 100;
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ * After the DbCache object is created, if you want to change this property, you should only assign it
+ * with a DB connection object.
+ */
+ public $db = 'db';
+ /**
+ * @var string name of the DB table to store cache content.
+ * The table should be pre-created as follows:
+ *
+ * ~~~
+ * CREATE TABLE tbl_cache (
+ * id char(128) NOT NULL PRIMARY KEY,
+ * expire int(11),
+ * data BLOB
+ * );
+ * ~~~
+ *
+ * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
+ * that can be used for some popular DBMS:
+ *
+ * - MySQL: LONGBLOB
+ * - PostgreSQL: BYTEA
+ * - MSSQL: BLOB
+ *
+ * When using DbCache in a production server, we recommend you create a DB index for the 'expire'
+ * column in the cache table to improve the performance.
+ */
+ public $cacheTable = '{{%cache}}';
+ /**
+ * @var integer the probability (parts per million) that garbage collection (GC) should be performed
+ * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
+ * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
+ **/
+ public $gcProbability = 100;
+ /**
+ * Initializes the DbCache component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException("DbCache::db must be either a DB connection instance or the application component ID of a DB connection.");
+ }
+ }
- /**
- * Initializes the DbCache component.
- * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
- * @throws InvalidConfigException if [[db]] is invalid.
- */
- public function init()
- {
- parent::init();
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!$this->db instanceof Connection) {
- throw new InvalidConfigException("DbCache::db must be either a DB connection instance or the application component ID of a DB connection.");
- }
- }
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
- /**
- * Checks whether a specified key exists in the cache.
- * This can be faster than getting the value from the cache if the data is big.
- * Note that this method does not check whether the dependency associated
- * with the cached data, if there is any, has changed. So a call to [[get]]
- * may return false while exists returns true.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
- */
- public function exists($key)
- {
- $key = $this->buildKey($key);
+ $query = new Query;
+ $query->select(['COUNT(*)'])
+ ->from($this->cacheTable)
+ ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
+ if ($this->db->enableQueryCache) {
+ // temporarily disable and re-enable query caching
+ $this->db->enableQueryCache = false;
+ $result = $query->createCommand($this->db)->queryScalar();
+ $this->db->enableQueryCache = true;
+ } else {
+ $result = $query->createCommand($this->db)->queryScalar();
+ }
- $query = new Query;
- $query->select(['COUNT(*)'])
- ->from($this->cacheTable)
- ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
- if ($this->db->enableQueryCache) {
- // temporarily disable and re-enable query caching
- $this->db->enableQueryCache = false;
- $result = $query->createCommand($this->db)->queryScalar();
- $this->db->enableQueryCache = true;
- } else {
- $result = $query->createCommand($this->db)->queryScalar();
- }
- return $result > 0;
- }
+ return $result > 0;
+ }
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- $query = new Query;
- $query->select(['data'])
- ->from($this->cacheTable)
- ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
- if ($this->db->enableQueryCache) {
- // temporarily disable and re-enable query caching
- $this->db->enableQueryCache = false;
- $result = $query->createCommand($this->db)->queryScalar();
- $this->db->enableQueryCache = true;
- return $result;
- } else {
- return $query->createCommand($this->db)->queryScalar();
- }
- }
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ $query = new Query;
+ $query->select(['data'])
+ ->from($this->cacheTable)
+ ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
+ if ($this->db->enableQueryCache) {
+ // temporarily disable and re-enable query caching
+ $this->db->enableQueryCache = false;
+ $result = $query->createCommand($this->db)->queryScalar();
+ $this->db->enableQueryCache = true;
- /**
- * Retrieves multiple values from cache with the specified keys.
- * @param array $keys a list of keys identifying the cached values
- * @return array a list of cached values indexed by the keys
- */
- protected function getValues($keys)
- {
- if (empty($keys)) {
- return [];
- }
- $query = new Query;
- $query->select(['id', 'data'])
- ->from($this->cacheTable)
- ->where(['id' => $keys])
- ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')');
+ return $result;
+ } else {
+ return $query->createCommand($this->db)->queryScalar();
+ }
+ }
- if ($this->db->enableQueryCache) {
- $this->db->enableQueryCache = false;
- $rows = $query->createCommand($this->db)->queryAll();
- $this->db->enableQueryCache = true;
- } else {
- $rows = $query->createCommand($this->db)->queryAll();
- }
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ if (empty($keys)) {
+ return [];
+ }
+ $query = new Query;
+ $query->select(['id', 'data'])
+ ->from($this->cacheTable)
+ ->where(['id' => $keys])
+ ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')');
- $results = [];
- foreach ($keys as $key) {
- $results[$key] = false;
- }
- foreach ($rows as $row) {
- $results[$row['id']] = $row['data'];
- }
- return $results;
- }
+ if ($this->db->enableQueryCache) {
+ $this->db->enableQueryCache = false;
+ $rows = $query->createCommand($this->db)->queryAll();
+ $this->db->enableQueryCache = true;
+ } else {
+ $rows = $query->createCommand($this->db)->queryAll();
+ }
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- $command = $this->db->createCommand()
- ->update($this->cacheTable, [
- 'expire' => $expire > 0 ? $expire + time() : 0,
- 'data' => [$value, \PDO::PARAM_LOB],
- ], ['id' => $key]);
+ $results = [];
+ foreach ($keys as $key) {
+ $results[$key] = false;
+ }
+ foreach ($rows as $row) {
+ $results[$row['id']] = $row['data'];
+ }
- if ($command->execute()) {
- $this->gc();
- return true;
- } else {
- return $this->addValue($key, $value, $expire);
- }
- }
+ return $results;
+ }
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- $this->gc();
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ $command = $this->db->createCommand()
+ ->update($this->cacheTable, [
+ 'expire' => $expire > 0 ? $expire + time() : 0,
+ 'data' => [$value, \PDO::PARAM_LOB],
+ ], ['id' => $key]);
- if ($expire > 0) {
- $expire += time();
- } else {
- $expire = 0;
- }
+ if ($command->execute()) {
+ $this->gc();
- try {
- $this->db->createCommand()
- ->insert($this->cacheTable, [
- 'id' => $key,
- 'expire' => $expire,
- 'data' => [$value, \PDO::PARAM_LOB],
- ])->execute();
- return true;
- } catch (\Exception $e) {
- return false;
- }
- }
+ return true;
+ } else {
+ return $this->addValue($key, $value, $expire);
+ }
+ }
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- $this->db->createCommand()
- ->delete($this->cacheTable, ['id' => $key])
- ->execute();
- return true;
- }
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ $this->gc();
- /**
- * Removes the expired data values.
- * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
- * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
- */
- public function gc($force = false)
- {
- if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
- $this->db->createCommand()
- ->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
- ->execute();
- }
- }
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- $this->db->createCommand()
- ->delete($this->cacheTable)
- ->execute();
- return true;
- }
+ try {
+ $this->db->createCommand()
+ ->insert($this->cacheTable, [
+ 'id' => $key,
+ 'expire' => $expire,
+ 'data' => [$value, \PDO::PARAM_LOB],
+ ])->execute();
+
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ $this->db->createCommand()
+ ->delete($this->cacheTable, ['id' => $key])
+ ->execute();
+
+ return true;
+ }
+
+ /**
+ * Removes the expired data values.
+ * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
+ * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
+ */
+ public function gc($force = false)
+ {
+ if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+ $this->db->createCommand()
+ ->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
+ ->execute();
+ }
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ $this->db->createCommand()
+ ->delete($this->cacheTable)
+ ->execute();
+
+ return true;
+ }
}
diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php
index ac6f2e7d1e2..a705d89ed80 100644
--- a/framework/caching/DbDependency.php
+++ b/framework/caching/DbDependency.php
@@ -22,45 +22,46 @@
*/
class DbDependency extends Dependency
{
- /**
- * @var string the application component ID of the DB connection.
- */
- public $db = 'db';
- /**
- * @var string the SQL query whose result is used to determine if the dependency has been changed.
- * Only the first row of the query result will be used.
- */
- public $sql;
- /**
- * @var array the parameters (name => value) to be bound to the SQL statement specified by [[sql]].
- */
- public $params = [];
+ /**
+ * @var string the application component ID of the DB connection.
+ */
+ public $db = 'db';
+ /**
+ * @var string the SQL query whose result is used to determine if the dependency has been changed.
+ * Only the first row of the query result will be used.
+ */
+ public $sql;
+ /**
+ * @var array the parameters (name => value) to be bound to the SQL statement specified by [[sql]].
+ */
+ public $params = [];
- /**
- * Generates the data needed to determine if dependency has been changed.
- * This method returns the value of the global state.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return mixed the data needed to determine if dependency has been changed.
- * @throws InvalidConfigException if [[db]] is not a valid application component ID
- */
- protected function generateDependencyData($cache)
- {
- $db = Yii::$app->getComponent($this->db);
- if (!$db instanceof Connection) {
- throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection.");
- }
- if ($this->sql === null) {
- throw new InvalidConfigException("DbDependency::sql must be set.");
- }
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method returns the value of the global state.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ * @throws InvalidConfigException if [[db]] is not a valid application component ID
+ */
+ protected function generateDependencyData($cache)
+ {
+ $db = Yii::$app->getComponent($this->db);
+ if (!$db instanceof Connection) {
+ throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection.");
+ }
+ if ($this->sql === null) {
+ throw new InvalidConfigException("DbDependency::sql must be set.");
+ }
- if ($db->enableQueryCache) {
- // temporarily disable and re-enable query caching
- $db->enableQueryCache = false;
- $result = $db->createCommand($this->sql, $this->params)->queryOne();
- $db->enableQueryCache = true;
- } else {
- $result = $db->createCommand($this->sql, $this->params)->queryOne();
- }
- return $result;
- }
+ if ($db->enableQueryCache) {
+ // temporarily disable and re-enable query caching
+ $db->enableQueryCache = false;
+ $result = $db->createCommand($this->sql, $this->params)->queryOne();
+ $db->enableQueryCache = true;
+ } else {
+ $result = $db->createCommand($this->sql, $this->params)->queryOne();
+ }
+
+ return $result;
+ }
}
diff --git a/framework/caching/Dependency.php b/framework/caching/Dependency.php
index c84f72ddf5a..9946955d31c 100644
--- a/framework/caching/Dependency.php
+++ b/framework/caching/Dependency.php
@@ -18,82 +18,82 @@
*/
abstract class Dependency extends \yii\base\Object
{
- /**
- * @var mixed the dependency data that is saved in cache and later is compared with the
- * latest dependency data.
- */
- public $data;
- /**
- * @var boolean whether this dependency is reusable or not. True value means that dependent
- * data for this cache dependency will be generated only once per request. This allows you
- * to use the same cache dependency for multiple separate cache calls while generating the same
- * page without an overhead of re-evaluating dependency data each time. Defaults to false.
- */
- public $reusable = false;
+ /**
+ * @var mixed the dependency data that is saved in cache and later is compared with the
+ * latest dependency data.
+ */
+ public $data;
+ /**
+ * @var boolean whether this dependency is reusable or not. True value means that dependent
+ * data for this cache dependency will be generated only once per request. This allows you
+ * to use the same cache dependency for multiple separate cache calls while generating the same
+ * page without an overhead of re-evaluating dependency data each time. Defaults to false.
+ */
+ public $reusable = false;
- /**
- * @var array static storage of cached data for reusable dependencies.
- */
- private static $_reusableData = [];
- /**
- * @var string a unique hash value for this cache dependency.
- */
- private $_hash;
+ /**
+ * @var array static storage of cached data for reusable dependencies.
+ */
+ private static $_reusableData = [];
+ /**
+ * @var string a unique hash value for this cache dependency.
+ */
+ private $_hash;
+ /**
+ * Evaluates the dependency by generating and saving the data related with dependency.
+ * This method is invoked by cache before writing data into it.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ */
+ public function evaluateDependency($cache)
+ {
+ if (!$this->reusable) {
+ $this->data = $this->generateDependencyData($cache);
+ } else {
+ if ($this->_hash === null) {
+ $this->_hash = sha1(serialize($this));
+ }
+ if (!array_key_exists($this->_hash, self::$_reusableData)) {
+ self::$_reusableData[$this->_hash] = $this->generateDependencyData($cache);
+ }
+ $this->data = self::$_reusableData[$this->_hash];
+ }
+ }
- /**
- * Evaluates the dependency by generating and saving the data related with dependency.
- * This method is invoked by cache before writing data into it.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- */
- public function evaluateDependency($cache)
- {
- if (!$this->reusable) {
- $this->data = $this->generateDependencyData($cache);
- } else {
- if ($this->_hash === null) {
- $this->_hash = sha1(serialize($this));
- }
- if (!array_key_exists($this->_hash, self::$_reusableData)) {
- self::$_reusableData[$this->_hash] = $this->generateDependencyData($cache);
- }
- $this->data = self::$_reusableData[$this->_hash];
- }
- }
+ /**
+ * Returns a value indicating whether the dependency has changed.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return boolean whether the dependency has changed.
+ */
+ public function getHasChanged($cache)
+ {
+ if (!$this->reusable) {
+ return $this->generateDependencyData($cache) !== $this->data;
+ } else {
+ if ($this->_hash === null) {
+ $this->_hash = sha1(serialize($this));
+ }
+ if (!array_key_exists($this->_hash, self::$_reusableData)) {
+ self::$_reusableData[$this->_hash] = $this->generateDependencyData($cache);
+ }
- /**
- * Returns a value indicating whether the dependency has changed.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return boolean whether the dependency has changed.
- */
- public function getHasChanged($cache)
- {
- if (!$this->reusable) {
- return $this->generateDependencyData($cache) !== $this->data;
- } else {
- if ($this->_hash === null) {
- $this->_hash = sha1(serialize($this));
- }
- if (!array_key_exists($this->_hash, self::$_reusableData)) {
- self::$_reusableData[$this->_hash] = $this->generateDependencyData($cache);
- }
- return self::$_reusableData[$this->_hash] !== $this->data;
- }
- }
+ return self::$_reusableData[$this->_hash] !== $this->data;
+ }
+ }
- /**
- * Resets all cached data for reusable dependencies.
- */
- public static function resetReusableData()
- {
- self::$_reusableData = [];
- }
+ /**
+ * Resets all cached data for reusable dependencies.
+ */
+ public static function resetReusableData()
+ {
+ self::$_reusableData = [];
+ }
- /**
- * Generates the data needed to determine if dependency has been changed.
- * Derived classes should override this method to generate the actual dependency data.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return mixed the data needed to determine if dependency has been changed.
- */
- abstract protected function generateDependencyData($cache);
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * Derived classes should override this method to generate the actual dependency data.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ abstract protected function generateDependencyData($cache);
}
diff --git a/framework/caching/DummyCache.php b/framework/caching/DummyCache.php
index 8d900dfedc0..5757874462d 100644
--- a/framework/caching/DummyCache.php
+++ b/framework/caching/DummyCache.php
@@ -20,62 +20,62 @@
*/
class DummyCache extends Cache
{
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- return false;
- }
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return false;
+ }
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- return true;
- }
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return true;
+ }
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- return true;
- }
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return true;
+ }
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- return true;
- }
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return true;
+ }
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- return true;
- }
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return true;
+ }
}
diff --git a/framework/caching/ExpressionDependency.php b/framework/caching/ExpressionDependency.php
index f6642b403c8..e886483bc2e 100644
--- a/framework/caching/ExpressionDependency.php
+++ b/framework/caching/ExpressionDependency.php
@@ -22,26 +22,26 @@
*/
class ExpressionDependency extends Dependency
{
- /**
- * @var string the string representation of a PHP expression whose result is used to determine the dependency.
- * A PHP expression can be any PHP code that evaluates to a value. To learn more about what an expression is,
- * please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php).
- */
- public $expression = 'true';
- /**
- * @var mixed custom parameters associated with this dependency. You may get the value
- * of this property in [[expression]] using `$this->params`.
- */
- public $params;
+ /**
+ * @var string the string representation of a PHP expression whose result is used to determine the dependency.
+ * A PHP expression can be any PHP code that evaluates to a value. To learn more about what an expression is,
+ * please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php).
+ */
+ public $expression = 'true';
+ /**
+ * @var mixed custom parameters associated with this dependency. You may get the value
+ * of this property in [[expression]] using `$this->params`.
+ */
+ public $params;
- /**
- * Generates the data needed to determine if dependency has been changed.
- * This method returns the result of the PHP expression.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return mixed the data needed to determine if dependency has been changed.
- */
- protected function generateDependencyData($cache)
- {
- return eval("return {$this->expression};");
- }
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method returns the result of the PHP expression.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ protected function generateDependencyData($cache)
+ {
+ return eval("return {$this->expression};");
+ }
}
diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php
index 14efa6888e2..e9e65105522 100644
--- a/framework/caching/FileCache.php
+++ b/framework/caching/FileCache.php
@@ -24,225 +24,230 @@
*/
class FileCache extends Cache
{
- /**
- * @var string a string prefixed to every cache key. This is needed when you store
- * cache data under the same [[cachePath]] for different applications to avoid
- * conflict.
- *
- * To ensure interoperability, only alphanumeric characters should be used.
- */
- public $keyPrefix = '';
- /**
- * @var string the directory to store cache files. You may use path alias here.
- * If not set, it will use the "cache" subdirectory under the application runtime path.
- */
- public $cachePath = '@runtime/cache';
- /**
- * @var string cache file suffix. Defaults to '.bin'.
- */
- public $cacheFileSuffix = '.bin';
- /**
- * @var integer the level of sub-directories to store cache files. Defaults to 1.
- * If the system has huge number of cache files (e.g. one million), you may use a bigger value
- * (usually no bigger than 3). Using sub-directories is mainly to ensure the file system
- * is not over burdened with a single directory having too many files.
- */
- public $directoryLevel = 1;
- /**
- * @var integer the probability (parts per million) that garbage collection (GC) should be performed
- * when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance.
- * This number should be between 0 and 1000000. A value 0 means no GC will be performed at all.
- **/
- public $gcProbability = 10;
- /**
- * @var integer the permission to be set for newly created cache files.
- * This value will be used by PHP chmod() function. No umask will be applied.
- * If not set, the permission will be determined by the current environment.
- */
- public $fileMode;
- /**
- * @var integer the permission to be set for newly created directories.
- * This value will be used by PHP chmod() function. No umask will be applied.
- * Defaults to 0775, meaning the directory is read-writable by owner and group,
- * but read-only for other users.
- */
- public $dirMode = 0775;
-
-
- /**
- * Initializes this component by ensuring the existence of the cache path.
- */
- public function init()
- {
- parent::init();
- $this->cachePath = Yii::getAlias($this->cachePath);
- if (!is_dir($this->cachePath)) {
- FileHelper::createDirectory($this->cachePath, $this->dirMode, true);
- }
- }
-
- /**
- * Checks whether a specified key exists in the cache.
- * This can be faster than getting the value from the cache if the data is big.
- * Note that this method does not check whether the dependency associated
- * with the cached data, if there is any, has changed. So a call to [[get]]
- * may return false while exists returns true.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
- */
- public function exists($key)
- {
- $cacheFile = $this->getCacheFile($this->buildKey($key));
- return @filemtime($cacheFile) > time();
- }
-
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- $cacheFile = $this->getCacheFile($key);
- if (@filemtime($cacheFile) > time()) {
- return @file_get_contents($cacheFile);
- } else {
- return false;
- }
- }
-
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- if ($expire <= 0) {
- $expire = 31536000; // 1 year
- }
- $expire += time();
-
- $cacheFile = $this->getCacheFile($key);
- if ($this->directoryLevel > 0) {
- @FileHelper::createDirectory(dirname($cacheFile), $this->dirMode, true);
- }
- if (@file_put_contents($cacheFile, $value, LOCK_EX) !== false) {
- if ($this->fileMode !== null) {
- @chmod($cacheFile, $this->fileMode);
- }
- return @touch($cacheFile, $expire);
- } else {
- return false;
- }
- }
-
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- $cacheFile = $this->getCacheFile($key);
- if (@filemtime($cacheFile) > time()) {
- return false;
- }
- return $this->setValue($key, $value, $expire);
- }
-
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- $cacheFile = $this->getCacheFile($key);
- return @unlink($cacheFile);
- }
-
- /**
- * Returns the cache file path given the cache key.
- * @param string $key cache key
- * @return string the cache file path
- */
- protected function getCacheFile($key)
- {
- if ($this->directoryLevel > 0) {
- $base = $this->cachePath;
- for ($i = 0; $i < $this->directoryLevel; ++$i) {
- if (($prefix = substr($key, $i + $i, 2)) !== false) {
- $base .= DIRECTORY_SEPARATOR . $prefix;
- }
- }
- return $base . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
- } else {
- return $this->cachePath . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
- }
- }
-
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- $this->gc(true, false);
- return true;
- }
-
- /**
- * Removes expired cache files.
- * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
- * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
- * @param boolean $expiredOnly whether to removed expired cache files only.
- * If true, all cache files under [[cachePath]] will be removed.
- */
- public function gc($force = false, $expiredOnly = true)
- {
- if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
- $this->gcRecursive($this->cachePath, $expiredOnly);
- }
- }
-
- /**
- * Recursively removing expired cache files under a directory.
- * This method is mainly used by [[gc()]].
- * @param string $path the directory under which expired cache files are removed.
- * @param boolean $expiredOnly whether to only remove expired cache files. If false, all files
- * under `$path` will be removed.
- */
- protected function gcRecursive($path, $expiredOnly)
- {
- if (($handle = opendir($path)) !== false) {
- while (($file = readdir($handle)) !== false) {
- if ($file[0] === '.') {
- continue;
- }
- $fullPath = $path . DIRECTORY_SEPARATOR . $file;
- if (is_dir($fullPath)) {
- $this->gcRecursive($fullPath, $expiredOnly);
- if (!$expiredOnly) {
- @rmdir($fullPath);
- }
- } elseif (!$expiredOnly || $expiredOnly && @filemtime($fullPath) < time()) {
- @unlink($fullPath);
- }
- }
- closedir($handle);
- }
- }
+ /**
+ * @var string a string prefixed to every cache key. This is needed when you store
+ * cache data under the same [[cachePath]] for different applications to avoid
+ * conflict.
+ *
+ * To ensure interoperability, only alphanumeric characters should be used.
+ */
+ public $keyPrefix = '';
+ /**
+ * @var string the directory to store cache files. You may use path alias here.
+ * If not set, it will use the "cache" subdirectory under the application runtime path.
+ */
+ public $cachePath = '@runtime/cache';
+ /**
+ * @var string cache file suffix. Defaults to '.bin'.
+ */
+ public $cacheFileSuffix = '.bin';
+ /**
+ * @var integer the level of sub-directories to store cache files. Defaults to 1.
+ * If the system has huge number of cache files (e.g. one million), you may use a bigger value
+ * (usually no bigger than 3). Using sub-directories is mainly to ensure the file system
+ * is not over burdened with a single directory having too many files.
+ */
+ public $directoryLevel = 1;
+ /**
+ * @var integer the probability (parts per million) that garbage collection (GC) should be performed
+ * when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance.
+ * This number should be between 0 and 1000000. A value 0 means no GC will be performed at all.
+ **/
+ public $gcProbability = 10;
+ /**
+ * @var integer the permission to be set for newly created cache files.
+ * This value will be used by PHP chmod() function. No umask will be applied.
+ * If not set, the permission will be determined by the current environment.
+ */
+ public $fileMode;
+ /**
+ * @var integer the permission to be set for newly created directories.
+ * This value will be used by PHP chmod() function. No umask will be applied.
+ * Defaults to 0775, meaning the directory is read-writable by owner and group,
+ * but read-only for other users.
+ */
+ public $dirMode = 0775;
+
+ /**
+ * Initializes this component by ensuring the existence of the cache path.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->cachePath = Yii::getAlias($this->cachePath);
+ if (!is_dir($this->cachePath)) {
+ FileHelper::createDirectory($this->cachePath, $this->dirMode, true);
+ }
+ }
+
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $cacheFile = $this->getCacheFile($this->buildKey($key));
+
+ return @filemtime($cacheFile) > time();
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ $cacheFile = $this->getCacheFile($key);
+ if (@filemtime($cacheFile) > time()) {
+ return @file_get_contents($cacheFile);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ if ($expire <= 0) {
+ $expire = 31536000; // 1 year
+ }
+ $expire += time();
+
+ $cacheFile = $this->getCacheFile($key);
+ if ($this->directoryLevel > 0) {
+ @FileHelper::createDirectory(dirname($cacheFile), $this->dirMode, true);
+ }
+ if (@file_put_contents($cacheFile, $value, LOCK_EX) !== false) {
+ if ($this->fileMode !== null) {
+ @chmod($cacheFile, $this->fileMode);
+ }
+
+ return @touch($cacheFile, $expire);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ $cacheFile = $this->getCacheFile($key);
+ if (@filemtime($cacheFile) > time()) {
+ return false;
+ }
+
+ return $this->setValue($key, $value, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ $cacheFile = $this->getCacheFile($key);
+
+ return @unlink($cacheFile);
+ }
+
+ /**
+ * Returns the cache file path given the cache key.
+ * @param string $key cache key
+ * @return string the cache file path
+ */
+ protected function getCacheFile($key)
+ {
+ if ($this->directoryLevel > 0) {
+ $base = $this->cachePath;
+ for ($i = 0; $i < $this->directoryLevel; ++$i) {
+ if (($prefix = substr($key, $i + $i, 2)) !== false) {
+ $base .= DIRECTORY_SEPARATOR . $prefix;
+ }
+ }
+
+ return $base . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
+ } else {
+ return $this->cachePath . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
+ }
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ $this->gc(true, false);
+
+ return true;
+ }
+
+ /**
+ * Removes expired cache files.
+ * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
+ * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
+ * @param boolean $expiredOnly whether to removed expired cache files only.
+ * If true, all cache files under [[cachePath]] will be removed.
+ */
+ public function gc($force = false, $expiredOnly = true)
+ {
+ if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+ $this->gcRecursive($this->cachePath, $expiredOnly);
+ }
+ }
+
+ /**
+ * Recursively removing expired cache files under a directory.
+ * This method is mainly used by [[gc()]].
+ * @param string $path the directory under which expired cache files are removed.
+ * @param boolean $expiredOnly whether to only remove expired cache files. If false, all files
+ * under `$path` will be removed.
+ */
+ protected function gcRecursive($path, $expiredOnly)
+ {
+ if (($handle = opendir($path)) !== false) {
+ while (($file = readdir($handle)) !== false) {
+ if ($file[0] === '.') {
+ continue;
+ }
+ $fullPath = $path . DIRECTORY_SEPARATOR . $file;
+ if (is_dir($fullPath)) {
+ $this->gcRecursive($fullPath, $expiredOnly);
+ if (!$expiredOnly) {
+ @rmdir($fullPath);
+ }
+ } elseif (!$expiredOnly || $expiredOnly && @filemtime($fullPath) < time()) {
+ @unlink($fullPath);
+ }
+ }
+ closedir($handle);
+ }
+ }
}
diff --git a/framework/caching/FileDependency.php b/framework/caching/FileDependency.php
index 200cdde9bbb..bd48e0a04a6 100644
--- a/framework/caching/FileDependency.php
+++ b/framework/caching/FileDependency.php
@@ -20,24 +20,25 @@
*/
class FileDependency extends Dependency
{
- /**
- * @var string the name of the file whose last modification time is used to
- * check if the dependency has been changed.
- */
- public $fileName;
+ /**
+ * @var string the name of the file whose last modification time is used to
+ * check if the dependency has been changed.
+ */
+ public $fileName;
- /**
- * Generates the data needed to determine if dependency has been changed.
- * This method returns the file's last modification time.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return mixed the data needed to determine if dependency has been changed.
- * @throws InvalidConfigException if [[fileName]] is not set
- */
- protected function generateDependencyData($cache)
- {
- if ($this->fileName === null) {
- throw new InvalidConfigException('FileDependency::fileName must be set');
- }
- return @filemtime($this->fileName);
- }
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method returns the file's last modification time.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ * @throws InvalidConfigException if [[fileName]] is not set
+ */
+ protected function generateDependencyData($cache)
+ {
+ if ($this->fileName === null) {
+ throw new InvalidConfigException('FileDependency::fileName must be set');
+ }
+
+ return @filemtime($this->fileName);
+ }
}
diff --git a/framework/caching/GroupDependency.php b/framework/caching/GroupDependency.php
index bcac858f27d..5129ac18cca 100644
--- a/framework/caching/GroupDependency.php
+++ b/framework/caching/GroupDependency.php
@@ -20,55 +20,58 @@
*/
class GroupDependency extends Dependency
{
- /**
- * @var string the group name. This property must be set.
- */
- public $group;
+ /**
+ * @var string the group name. This property must be set.
+ */
+ public $group;
- /**
- * Generates the data needed to determine if dependency has been changed.
- * This method does nothing in this class.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return mixed the data needed to determine if dependency has been changed.
- * @throws InvalidConfigException if [[group]] is not set.
- */
- protected function generateDependencyData($cache)
- {
- if ($this->group === null) {
- throw new InvalidConfigException('GroupDependency::group must be set');
- }
- $version = $cache->get([__CLASS__, $this->group]);
- if ($version === false) {
- $version = $this->invalidate($cache, $this->group);
- }
- return $version;
- }
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method does nothing in this class.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ * @throws InvalidConfigException if [[group]] is not set.
+ */
+ protected function generateDependencyData($cache)
+ {
+ if ($this->group === null) {
+ throw new InvalidConfigException('GroupDependency::group must be set');
+ }
+ $version = $cache->get([__CLASS__, $this->group]);
+ if ($version === false) {
+ $version = $this->invalidate($cache, $this->group);
+ }
- /**
- * Performs the actual dependency checking.
- * @param Cache $cache the cache component that is currently evaluating this dependency
- * @return boolean whether the dependency is changed or not.
- * @throws InvalidConfigException if [[group]] is not set.
- */
- public function getHasChanged($cache)
- {
- if ($this->group === null) {
- throw new InvalidConfigException('GroupDependency::group must be set');
- }
- $version = $cache->get([__CLASS__, $this->group]);
- return $version === false || $version !== $this->data;
- }
+ return $version;
+ }
- /**
- * Invalidates all of the cached data items that have the same [[group]].
- * @param Cache $cache the cache component that caches the data items
- * @param string $group the group name
- * @return string the current version number
- */
- public static function invalidate($cache, $group)
- {
- $version = microtime();
- $cache->set([__CLASS__, $group], $version);
- return $version;
- }
+ /**
+ * Performs the actual dependency checking.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return boolean whether the dependency is changed or not.
+ * @throws InvalidConfigException if [[group]] is not set.
+ */
+ public function getHasChanged($cache)
+ {
+ if ($this->group === null) {
+ throw new InvalidConfigException('GroupDependency::group must be set');
+ }
+ $version = $cache->get([__CLASS__, $this->group]);
+
+ return $version === false || $version !== $this->data;
+ }
+
+ /**
+ * Invalidates all of the cached data items that have the same [[group]].
+ * @param Cache $cache the cache component that caches the data items
+ * @param string $group the group name
+ * @return string the current version number
+ */
+ public static function invalidate($cache, $group)
+ {
+ $version = microtime();
+ $cache->set([__CLASS__, $group], $version);
+
+ return $version;
+ }
}
diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php
index 339179673a5..4f53fe4aba6 100644
--- a/framework/caching/MemCache.php
+++ b/framework/caching/MemCache.php
@@ -62,204 +62,206 @@
*/
class MemCache extends Cache
{
- /**
- * @var boolean whether to use memcached or memcache as the underlying caching extension.
- * If true, [memcached](http://pecl.php.net/package/memcached) will be used.
- * If false, [memcache](http://pecl.php.net/package/memcache) will be used.
- * Defaults to false.
- */
- public $useMemcached = false;
- /**
- * @var \Memcache|\Memcached the Memcache instance
- */
- private $_cache = null;
- /**
- * @var array list of memcache server configurations
- */
- private $_servers = [];
+ /**
+ * @var boolean whether to use memcached or memcache as the underlying caching extension.
+ * If true, [memcached](http://pecl.php.net/package/memcached) will be used.
+ * If false, [memcache](http://pecl.php.net/package/memcache) will be used.
+ * Defaults to false.
+ */
+ public $useMemcached = false;
+ /**
+ * @var \Memcache|\Memcached the Memcache instance
+ */
+ private $_cache = null;
+ /**
+ * @var array list of memcache server configurations
+ */
+ private $_servers = [];
- /**
- * Initializes this application component.
- * It creates the memcache instance and adds memcache servers.
- */
- public function init()
- {
- parent::init();
- $servers = $this->getServers();
- $cache = $this->getMemCache();
- if (empty($servers)) {
- $cache->addServer('127.0.0.1', 11211);
- } else {
- if (!$this->useMemcached) {
- // different version of memcache may have different number of parameters for the addServer method.
- $class = new \ReflectionClass($cache);
- $paramCount = $class->getMethod('addServer')->getNumberOfParameters();
- }
- foreach ($servers as $server) {
- if ($server->host === null) {
- throw new InvalidConfigException("The 'host' property must be specified for every memcache server.");
- }
- if ($this->useMemcached) {
- $cache->addServer($server->host, $server->port, $server->weight);
- } else {
- // $timeout is used for memcache versions that do not have timeoutms parameter
- $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0);
- if ($paramCount === 9) {
- $cache->addServer(
- $server->host, $server->port, $server->persistent,
- $server->weight, $timeout, $server->retryInterval,
- $server->status, $server->failureCallback, $server->timeout
- );
- } else {
- $cache->addServer(
- $server->host, $server->port, $server->persistent,
- $server->weight, $timeout, $server->retryInterval,
- $server->status, $server->failureCallback
- );
- }
- }
- }
- }
- }
+ /**
+ * Initializes this application component.
+ * It creates the memcache instance and adds memcache servers.
+ */
+ public function init()
+ {
+ parent::init();
+ $servers = $this->getServers();
+ $cache = $this->getMemCache();
+ if (empty($servers)) {
+ $cache->addServer('127.0.0.1', 11211);
+ } else {
+ if (!$this->useMemcached) {
+ // different version of memcache may have different number of parameters for the addServer method.
+ $class = new \ReflectionClass($cache);
+ $paramCount = $class->getMethod('addServer')->getNumberOfParameters();
+ }
+ foreach ($servers as $server) {
+ if ($server->host === null) {
+ throw new InvalidConfigException("The 'host' property must be specified for every memcache server.");
+ }
+ if ($this->useMemcached) {
+ $cache->addServer($server->host, $server->port, $server->weight);
+ } else {
+ // $timeout is used for memcache versions that do not have timeoutms parameter
+ $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0);
+ if ($paramCount === 9) {
+ $cache->addServer(
+ $server->host, $server->port, $server->persistent,
+ $server->weight, $timeout, $server->retryInterval,
+ $server->status, $server->failureCallback, $server->timeout
+ );
+ } else {
+ $cache->addServer(
+ $server->host, $server->port, $server->persistent,
+ $server->weight, $timeout, $server->retryInterval,
+ $server->status, $server->failureCallback
+ );
+ }
+ }
+ }
+ }
+ }
- /**
- * Returns the underlying memcache (or memcached) object.
- * @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component.
- * @throws InvalidConfigException if memcache or memcached extension is not loaded
- */
- public function getMemcache()
- {
- if ($this->_cache === null) {
- $extension = $this->useMemcached ? 'memcached' : 'memcache';
- if (!extension_loaded($extension)) {
- throw new InvalidConfigException("MemCache requires PHP $extension extension to be loaded.");
- }
- $this->_cache = $this->useMemcached ? new \Memcached : new \Memcache;
- }
- return $this->_cache;
- }
+ /**
+ * Returns the underlying memcache (or memcached) object.
+ * @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component.
+ * @throws InvalidConfigException if memcache or memcached extension is not loaded
+ */
+ public function getMemcache()
+ {
+ if ($this->_cache === null) {
+ $extension = $this->useMemcached ? 'memcached' : 'memcache';
+ if (!extension_loaded($extension)) {
+ throw new InvalidConfigException("MemCache requires PHP $extension extension to be loaded.");
+ }
+ $this->_cache = $this->useMemcached ? new \Memcached : new \Memcache;
+ }
- /**
- * Returns the memcache server configurations.
- * @return MemCacheServer[] list of memcache server configurations.
- */
- public function getServers()
- {
- return $this->_servers;
- }
+ return $this->_cache;
+ }
- /**
- * @param array $config list of memcache server configurations. Each element must be an array
- * with the following keys: host, port, persistent, weight, timeout, retryInterval, status.
- * @see http://www.php.net/manual/en/function.Memcache-addServer.php
- */
- public function setServers($config)
- {
- foreach ($config as $c) {
- $this->_servers[] = new MemCacheServer($c);
- }
- }
+ /**
+ * Returns the memcache server configurations.
+ * @return MemCacheServer[] list of memcache server configurations.
+ */
+ public function getServers()
+ {
+ return $this->_servers;
+ }
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- return $this->_cache->get($key);
- }
+ /**
+ * @param array $config list of memcache server configurations. Each element must be an array
+ * with the following keys: host, port, persistent, weight, timeout, retryInterval, status.
+ * @see http://www.php.net/manual/en/function.Memcache-addServer.php
+ */
+ public function setServers($config)
+ {
+ foreach ($config as $c) {
+ $this->_servers[] = new MemCacheServer($c);
+ }
+ }
- /**
- * Retrieves multiple values from cache with the specified keys.
- * @param array $keys a list of keys identifying the cached values
- * @return array a list of cached values indexed by the keys
- */
- protected function getValues($keys)
- {
- return $this->useMemcached ? $this->_cache->getMulti($keys) : $this->_cache->get($keys);
- }
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return $this->_cache->get($key);
+ }
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- if ($expire > 0) {
- $expire += time();
- } else {
- $expire = 0;
- }
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ return $this->useMemcached ? $this->_cache->getMulti($keys) : $this->_cache->get($keys);
+ }
- return $this->useMemcached ? $this->_cache->set($key, $value, $expire) : $this->_cache->set($key, $value, 0, $expire);
- }
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
- /**
- * Stores multiple key-value pairs in cache.
- * @param array $data array where key corresponds to cache key while value is the value stored
- * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
- * @return array array of failed keys. Always empty in case of using memcached.
- */
- protected function setValues($data, $expire)
- {
- if ($this->useMemcached) {
- if ($expire > 0) {
- $expire += time();
- } else {
- $expire = 0;
- }
- $this->_cache->setMulti($data, $expire);
- return [];
- } else {
- return parent::setValues($data, $expire);
- }
- }
+ return $this->useMemcached ? $this->_cache->set($key, $value, $expire) : $this->_cache->set($key, $value, 0, $expire);
+ }
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- if ($expire > 0) {
- $expire += time();
- } else {
- $expire = 0;
- }
+ /**
+ * Stores multiple key-value pairs in cache.
+ * @param array $data array where key corresponds to cache key while value is the value stored
+ * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
+ * @return array array of failed keys. Always empty in case of using memcached.
+ */
+ protected function setValues($data, $expire)
+ {
+ if ($this->useMemcached) {
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
+ $this->_cache->setMulti($data, $expire);
- return $this->useMemcached ? $this->_cache->add($key, $value, $expire) : $this->_cache->add($key, $value, 0, $expire);
- }
+ return [];
+ } else {
+ return parent::setValues($data, $expire);
+ }
+ }
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- return $this->_cache->delete($key, 0);
- }
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- return $this->_cache->flush();
- }
+ return $this->useMemcached ? $this->_cache->add($key, $value, $expire) : $this->_cache->add($key, $value, 0, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return $this->_cache->delete($key, 0);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return $this->_cache->flush();
+ }
}
diff --git a/framework/caching/MemCacheServer.php b/framework/caching/MemCacheServer.php
index b1b77278c40..05da7a33ea6 100644
--- a/framework/caching/MemCacheServer.php
+++ b/framework/caching/MemCacheServer.php
@@ -18,41 +18,41 @@
*/
class MemCacheServer extends \yii\base\Object
{
- /**
- * @var string memcache server hostname or IP address
- */
- public $host;
- /**
- * @var integer memcache server port
- */
- public $port = 11211;
- /**
- * @var integer probability of using this server among all servers.
- */
- public $weight = 1;
- /**
- * @var boolean whether to use a persistent connection. This is used by memcache only.
- */
- public $persistent = true;
- /**
- * @var integer timeout in milliseconds which will be used for connecting to the server.
- * This is used by memcache only. For old versions of memcache that only support specifying
- * timeout in seconds this will be rounded up to full seconds.
- */
- public $timeout = 1000;
- /**
- * @var integer how often a failed server will be retried (in seconds). This is used by memcache only.
- */
- public $retryInterval = 15;
- /**
- * @var boolean if the server should be flagged as online upon a failure. This is used by memcache only.
- */
- public $status = true;
- /**
- * @var \Closure this callback function will run upon encountering an error.
- * The callback is run before fail over is attempted. The function takes two parameters,
- * the [[host]] and the [[port]] of the failed server.
- * This is used by memcache only.
- */
- public $failureCallback;
+ /**
+ * @var string memcache server hostname or IP address
+ */
+ public $host;
+ /**
+ * @var integer memcache server port
+ */
+ public $port = 11211;
+ /**
+ * @var integer probability of using this server among all servers.
+ */
+ public $weight = 1;
+ /**
+ * @var boolean whether to use a persistent connection. This is used by memcache only.
+ */
+ public $persistent = true;
+ /**
+ * @var integer timeout in milliseconds which will be used for connecting to the server.
+ * This is used by memcache only. For old versions of memcache that only support specifying
+ * timeout in seconds this will be rounded up to full seconds.
+ */
+ public $timeout = 1000;
+ /**
+ * @var integer how often a failed server will be retried (in seconds). This is used by memcache only.
+ */
+ public $retryInterval = 15;
+ /**
+ * @var boolean if the server should be flagged as online upon a failure. This is used by memcache only.
+ */
+ public $status = true;
+ /**
+ * @var \Closure this callback function will run upon encountering an error.
+ * The callback is run before fail over is attempted. The function takes two parameters,
+ * the [[host]] and the [[port]] of the failed server.
+ * This is used by memcache only.
+ */
+ public $failureCallback;
}
diff --git a/framework/caching/WinCache.php b/framework/caching/WinCache.php
index 6b80f3cc047..4c611c2f8ff 100644
--- a/framework/caching/WinCache.php
+++ b/framework/caching/WinCache.php
@@ -20,113 +20,114 @@
*/
class WinCache extends Cache
{
- /**
- * Checks whether a specified key exists in the cache.
- * This can be faster than getting the value from the cache if the data is big.
- * Note that this method does not check whether the dependency associated
- * with the cached data, if there is any, has changed. So a call to [[get]]
- * may return false while exists returns true.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
- */
- public function exists($key)
- {
- $key = $this->buildKey($key);
- return wincache_ucache_exists($key);
- }
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- return wincache_ucache_get($key);
- }
+ return wincache_ucache_exists($key);
+ }
- /**
- * Retrieves multiple values from cache with the specified keys.
- * @param array $keys a list of keys identifying the cached values
- * @return array a list of cached values indexed by the keys
- */
- protected function getValues($keys)
- {
- return wincache_ucache_get($keys);
- }
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return wincache_ucache_get($key);
+ }
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- return wincache_ucache_set($key, $value, $expire);
- }
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ return wincache_ucache_get($keys);
+ }
- /**
- * Stores multiple key-value pairs in cache.
- * @param array $data array where key corresponds to cache key while value is the value stored
- * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
- * @return array array of failed keys
- */
- protected function setValues($data, $expire)
- {
- return wincache_ucache_set($data, null, $expire);
- }
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return wincache_ucache_set($key, $value, $expire);
+ }
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- return wincache_ucache_add($key, $value, $expire);
- }
+ /**
+ * Stores multiple key-value pairs in cache.
+ * @param array $data array where key corresponds to cache key while value is the value stored
+ * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
+ * @return array array of failed keys
+ */
+ protected function setValues($data, $expire)
+ {
+ return wincache_ucache_set($data, null, $expire);
+ }
- /**
- * Adds multiple key-value pairs to cache.
- * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache
- * storage supports multiadd, this method should be overridden to exploit that feature.
- * @param array $data array where key corresponds to cache key while value is the value stored
- * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
- * @return array array of failed keys
- */
- protected function addValues($data, $expire)
- {
- return wincache_ucache_add($data, null, $expire);
- }
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return wincache_ucache_add($key, $value, $expire);
+ }
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- return wincache_ucache_delete($key);
- }
+ /**
+ * Adds multiple key-value pairs to cache.
+ * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache
+ * storage supports multiadd, this method should be overridden to exploit that feature.
+ * @param array $data array where key corresponds to cache key while value is the value stored
+ * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
+ * @return array array of failed keys
+ */
+ protected function addValues($data, $expire)
+ {
+ return wincache_ucache_add($data, null, $expire);
+ }
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- return wincache_ucache_clear();
- }
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return wincache_ucache_delete($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return wincache_ucache_clear();
+ }
}
diff --git a/framework/caching/XCache.php b/framework/caching/XCache.php
index 4221a398631..370d4cea0c7 100644
--- a/framework/caching/XCache.php
+++ b/framework/caching/XCache.php
@@ -21,84 +21,86 @@
*/
class XCache extends Cache
{
- /**
- * Checks whether a specified key exists in the cache.
- * This can be faster than getting the value from the cache if the data is big.
- * Note that this method does not check whether the dependency associated
- * with the cached data, if there is any, has changed. So a call to [[get]]
- * may return false while exists returns true.
- * @param mixed $key a key identifying the cached value. This can be a simple string or
- * a complex data structure consisting of factors representing the key.
- * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
- */
- public function exists($key)
- {
- $key = $this->buildKey($key);
- return xcache_isset($key);
- }
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- return xcache_isset($key) ? xcache_get($key) : false;
- }
+ return xcache_isset($key);
+ }
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- return xcache_set($key, $value, $expire);
- }
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return xcache_isset($key) ? xcache_get($key) : false;
+ }
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- return !xcache_isset($key) ? $this->setValue($key, $value, $expire) : false;
- }
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return xcache_set($key, $value, $expire);
+ }
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- return xcache_unset($key);
- }
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return !xcache_isset($key) ? $this->setValue($key, $value, $expire) : false;
+ }
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++) {
- if (xcache_clear_cache(XC_TYPE_VAR, $i) === false) {
- return false;
- }
- }
- return true;
- }
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return xcache_unset($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++) {
+ if (xcache_clear_cache(XC_TYPE_VAR, $i) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/framework/caching/ZendDataCache.php b/framework/caching/ZendDataCache.php
index 9ff2fd057af..f06459eb0a1 100644
--- a/framework/caching/ZendDataCache.php
+++ b/framework/caching/ZendDataCache.php
@@ -20,64 +20,65 @@
*/
class ZendDataCache extends Cache
{
- /**
- * Retrieves a value from cache with a specified key.
- * This is the implementation of the method declared in the parent class.
- * @param string $key a unique key identifying the cached value
- * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
- */
- protected function getValue($key)
- {
- $result = zend_shm_cache_fetch($key);
- return $result === null ? false : $result;
- }
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ $result = zend_shm_cache_fetch($key);
- /**
- * Stores a value identified by a key in cache.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function setValue($key, $value, $expire)
- {
- return zend_shm_cache_store($key, $value, $expire);
- }
+ return $result === null ? false : $result;
+ }
- /**
- * Stores a value identified by a key into cache if the cache does not contain this key.
- * This is the implementation of the method declared in the parent class.
- *
- * @param string $key the key identifying the value to be cached
- * @param string $value the value to be cached
- * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
- * @return boolean true if the value is successfully stored into cache, false otherwise
- */
- protected function addValue($key, $value, $expire)
- {
- return zend_shm_cache_fetch($key) === null ? $this->setValue($key, $value, $expire) : false;
- }
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return zend_shm_cache_store($key, $value, $expire);
+ }
- /**
- * Deletes a value with the specified key from cache
- * This is the implementation of the method declared in the parent class.
- * @param string $key the key of the value to be deleted
- * @return boolean if no error happens during deletion
- */
- protected function deleteValue($key)
- {
- return zend_shm_cache_delete($key);
- }
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return zend_shm_cache_fetch($key) === null ? $this->setValue($key, $value, $expire) : false;
+ }
- /**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- */
- protected function flushValues()
- {
- return zend_shm_cache_clear();
- }
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return zend_shm_cache_delete($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return zend_shm_cache_clear();
+ }
}
diff --git a/framework/captcha/Captcha.php b/framework/captcha/Captcha.php
index 03e2145a84c..61bd75d1fad 100644
--- a/framework/captcha/Captcha.php
+++ b/framework/captcha/Captcha.php
@@ -34,109 +34,109 @@
*/
class Captcha extends InputWidget
{
- /**
- * @var string the route of the action that generates the CAPTCHA images.
- * The action represented by this route must be an action of [[CaptchaAction]].
- */
- public $captchaAction = 'site/captcha';
- /**
- * @var array HTML attributes to be applied to the CAPTCHA image tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $imageOptions = [];
- /**
- * @var string the template for arranging the CAPTCHA image tag and the text input tag.
- * In this template, the token `{image}` will be replaced with the actual image tag,
- * while `{input}` will be replaced with the text input tag.
- */
- public $template = '{image} {input}';
- /**
- * @var array the HTML attributes for the input tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'form-control'];
+ /**
+ * @var string the route of the action that generates the CAPTCHA images.
+ * The action represented by this route must be an action of [[CaptchaAction]].
+ */
+ public $captchaAction = 'site/captcha';
+ /**
+ * @var array HTML attributes to be applied to the CAPTCHA image tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $imageOptions = [];
+ /**
+ * @var string the template for arranging the CAPTCHA image tag and the text input tag.
+ * In this template, the token `{image}` will be replaced with the actual image tag,
+ * while `{input}` will be replaced with the text input tag.
+ */
+ public $template = '{image} {input}';
+ /**
+ * @var array the HTML attributes for the input tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'form-control'];
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
- /**
- * Initializes the widget.
- */
- public function init()
- {
- parent::init();
+ $this->checkRequirements();
- $this->checkRequirements();
+ if (!isset($this->imageOptions['id'])) {
+ $this->imageOptions['id'] = $this->options['id'] . '-image';
+ }
+ }
- if (!isset($this->imageOptions['id'])) {
- $this->imageOptions['id'] = $this->options['id'] . '-image';
- }
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ $this->registerClientScript();
+ if ($this->hasModel()) {
+ $input = Html::activeTextInput($this->model, $this->attribute, $this->options);
+ } else {
+ $input = Html::textInput($this->name, $this->value, $this->options);
+ }
+ $url = Yii::$app->getUrlManager()->createUrl([$this->captchaAction, 'v' => uniqid()]);
+ $image = Html::img($url, $this->imageOptions);
+ echo strtr($this->template, [
+ '{input}' => $input,
+ '{image}' => $image,
+ ]);
+ }
- /**
- * Renders the widget.
- */
- public function run()
- {
- $this->registerClientScript();
- if ($this->hasModel()) {
- $input = Html::activeTextInput($this->model, $this->attribute, $this->options);
- } else {
- $input = Html::textInput($this->name, $this->value, $this->options);
- }
- $url = Yii::$app->getUrlManager()->createUrl([$this->captchaAction, 'v' => uniqid()]);
- $image = Html::img($url, $this->imageOptions);
- echo strtr($this->template, [
- '{input}' => $input,
- '{image}' => $image,
- ]);
- }
+ /**
+ * Registers the needed JavaScript.
+ */
+ public function registerClientScript()
+ {
+ $options = $this->getClientOptions();
+ $options = empty($options) ? '' : Json::encode($options);
+ $id = $this->imageOptions['id'];
+ $view = $this->getView();
+ CaptchaAsset::register($view);
+ $view->registerJs("jQuery('#$id').yiiCaptcha($options);");
+ }
- /**
- * Registers the needed JavaScript.
- */
- public function registerClientScript()
- {
- $options = $this->getClientOptions();
- $options = empty($options) ? '' : Json::encode($options);
- $id = $this->imageOptions['id'];
- $view = $this->getView();
- CaptchaAsset::register($view);
- $view->registerJs("jQuery('#$id').yiiCaptcha($options);");
- }
+ /**
+ * Returns the options for the captcha JS widget.
+ * @return array the options
+ */
+ protected function getClientOptions()
+ {
+ $options = [
+ 'refreshUrl' => Url::to(['/' . $this->captchaAction, CaptchaAction::REFRESH_GET_VAR => 1]),
+ 'hashKey' => "yiiCaptcha/{$this->captchaAction}",
+ ];
- /**
- * Returns the options for the captcha JS widget.
- * @return array the options
- */
- protected function getClientOptions()
- {
- $options = [
- 'refreshUrl' => Url::to(['/' . $this->captchaAction, CaptchaAction::REFRESH_GET_VAR => 1]),
- 'hashKey' => "yiiCaptcha/{$this->captchaAction}",
- ];
- return $options;
- }
+ return $options;
+ }
- /**
- * Checks if there is graphic extension available to generate CAPTCHA images.
- * This method will check the existence of ImageMagick and GD extensions.
- * @return string the name of the graphic extension, either "imagick" or "gd".
- * @throws InvalidConfigException if neither ImageMagick nor GD is installed.
- */
- public static function checkRequirements()
- {
- if (extension_loaded('imagick')) {
- $imagick = new \Imagick();
- $imagickFormats = $imagick->queryFormats('PNG');
- if (in_array('PNG', $imagickFormats)) {
- return 'imagick';
- }
- }
- if (extension_loaded('gd')) {
- $gdInfo = gd_info();
- if (!empty($gdInfo['FreeType Support'])) {
- return 'gd';
- }
- }
- throw new InvalidConfigException('GD with FreeType or ImageMagick PHP extensions are required.');
- }
+ /**
+ * Checks if there is graphic extension available to generate CAPTCHA images.
+ * This method will check the existence of ImageMagick and GD extensions.
+ * @return string the name of the graphic extension, either "imagick" or "gd".
+ * @throws InvalidConfigException if neither ImageMagick nor GD is installed.
+ */
+ public static function checkRequirements()
+ {
+ if (extension_loaded('imagick')) {
+ $imagick = new \Imagick();
+ $imagickFormats = $imagick->queryFormats('PNG');
+ if (in_array('PNG', $imagickFormats)) {
+ return 'imagick';
+ }
+ }
+ if (extension_loaded('gd')) {
+ $gdInfo = gd_info();
+ if (!empty($gdInfo['FreeType Support'])) {
+ return 'gd';
+ }
+ }
+ throw new InvalidConfigException('GD with FreeType or ImageMagick PHP extensions are required.');
+ }
}
diff --git a/framework/captcha/CaptchaAction.php b/framework/captcha/CaptchaAction.php
index ad7958b83a0..ba2255371eb 100644
--- a/framework/captcha/CaptchaAction.php
+++ b/framework/captcha/CaptchaAction.php
@@ -37,304 +37,310 @@
*/
class CaptchaAction extends Action
{
- /**
- * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated.
- */
- const REFRESH_GET_VAR = 'refresh';
- /**
- * @var integer how many times should the same CAPTCHA be displayed. Defaults to 3.
- * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2).
- */
- public $testLimit = 3;
- /**
- * @var integer the width of the generated CAPTCHA image. Defaults to 120.
- */
- public $width = 120;
- /**
- * @var integer the height of the generated CAPTCHA image. Defaults to 50.
- */
- public $height = 50;
- /**
- * @var integer padding around the text. Defaults to 2.
- */
- public $padding = 2;
- /**
- * @var integer the background color. For example, 0x55FF00.
- * Defaults to 0xFFFFFF, meaning white color.
- */
- public $backColor = 0xFFFFFF;
- /**
- * @var integer the font color. For example, 0x55FF00. Defaults to 0x2040A0 (blue color).
- */
- public $foreColor = 0x2040A0;
- /**
- * @var boolean whether to use transparent background. Defaults to false.
- */
- public $transparent = false;
- /**
- * @var integer the minimum length for randomly generated word. Defaults to 6.
- */
- public $minLength = 6;
- /**
- * @var integer the maximum length for randomly generated word. Defaults to 7.
- */
- public $maxLength = 7;
- /**
- * @var integer the offset between characters. Defaults to -2. You can adjust this property
- * in order to decrease or increase the readability of the captcha.
- **/
- public $offset = -2;
- /**
- * @var string the TrueType font file. This can be either a file path or path alias.
- */
- public $fontFile = '@yii/captcha/SpicyRice.ttf';
- /**
- * @var string the fixed verification code. When this property is set,
- * [[getVerifyCode()]] will always return the value of this property.
- * This is mainly used in automated tests where we want to be able to reproduce
- * the same verification code each time we run the tests.
- * If not set, it means the verification code will be randomly generated.
- */
- public $fixedVerifyCode;
-
-
- /**
- * Initializes the action.
- * @throws InvalidConfigException if the font file does not exist.
- */
- public function init()
- {
- $this->fontFile = Yii::getAlias($this->fontFile);
- if (!is_file($this->fontFile)) {
- throw new InvalidConfigException("The font file does not exist: {$this->fontFile}");
- }
- }
-
- /**
- * Runs the action.
- */
- public function run()
- {
- if (Yii::$app->request->getQueryParam(self::REFRESH_GET_VAR) !== null) {
- // AJAX request for regenerating code
- $code = $this->getVerifyCode(true);
- return json_encode([
- 'hash1' => $this->generateValidationHash($code),
- 'hash2' => $this->generateValidationHash(strtolower($code)),
- // we add a random 'v' parameter so that FireFox can refresh the image
- // when src attribute of image tag is changed
- 'url' => Url::to([$this->id, 'v' => uniqid()]),
- ]);
- } else {
- $this->setHttpHeaders();
- return $this->renderImage($this->getVerifyCode());
- }
- }
-
- /**
- * Generates a hash code that can be used for client side validation.
- * @param string $code the CAPTCHA code
- * @return string a hash code generated from the CAPTCHA code
- */
- public function generateValidationHash($code)
- {
- for ($h = 0, $i = strlen($code) - 1; $i >= 0; --$i) {
- $h += ord($code[$i]);
- }
- return $h;
- }
-
- /**
- * Gets the verification code.
- * @param boolean $regenerate whether the verification code should be regenerated.
- * @return string the verification code.
- */
- public function getVerifyCode($regenerate = false)
- {
- if ($this->fixedVerifyCode !== null) {
- return $this->fixedVerifyCode;
- }
-
- $session = Yii::$app->getSession();
- $session->open();
- $name = $this->getSessionKey();
- if ($session[$name] === null || $regenerate) {
- $session[$name] = $this->generateVerifyCode();
- $session[$name . 'count'] = 1;
- }
- return $session[$name];
- }
-
- /**
- * Validates the input to see if it matches the generated code.
- * @param string $input user input
- * @param boolean $caseSensitive whether the comparison should be case-sensitive
- * @return boolean whether the input is valid
- */
- public function validate($input, $caseSensitive)
- {
- $code = $this->getVerifyCode();
- $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0;
- $session = Yii::$app->getSession();
- $session->open();
- $name = $this->getSessionKey() . 'count';
- $session[$name] = $session[$name] + 1;
- if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) {
- $this->getVerifyCode(true);
- }
- return $valid;
- }
-
- /**
- * Generates a new verification code.
- * @return string the generated verification code
- */
- protected function generateVerifyCode()
- {
- if ($this->minLength > $this->maxLength) {
- $this->maxLength = $this->minLength;
- }
- if ($this->minLength < 3) {
- $this->minLength = 3;
- }
- if ($this->maxLength > 20) {
- $this->maxLength = 20;
- }
- $length = mt_rand($this->minLength, $this->maxLength);
-
- $letters = 'bcdfghjklmnpqrstvwxyz';
- $vowels = 'aeiou';
- $code = '';
- for ($i = 0; $i < $length; ++$i) {
- if ($i % 2 && mt_rand(0, 10) > 2 || !($i % 2) && mt_rand(0, 10) > 9) {
- $code .= $vowels[mt_rand(0, 4)];
- } else {
- $code .= $letters[mt_rand(0, 20)];
- }
- }
-
- return $code;
- }
-
- /**
- * Returns the session variable name used to store verification code.
- * @return string the session variable name
- */
- protected function getSessionKey()
- {
- return '__captcha/' . $this->getUniqueId();
- }
-
- /**
- * Renders the CAPTCHA image.
- * @param string $code the verification code
- * @return string image contents
- */
- protected function renderImage($code)
- {
- if (Captcha::checkRequirements() === 'gd') {
- return $this->renderImageByGD($code);
- } else {
- return $this->renderImageByImagick($code);
- }
- }
-
- /**
- * Renders the CAPTCHA image based on the code using GD library.
- * @param string $code the verification code
- * @return string image contents
- */
- protected function renderImageByGD($code)
- {
- $image = imagecreatetruecolor($this->width, $this->height);
-
- $backColor = imagecolorallocate($image,
- (int)($this->backColor % 0x1000000 / 0x10000),
- (int)($this->backColor % 0x10000 / 0x100),
- $this->backColor % 0x100);
- imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backColor);
- imagecolordeallocate($image, $backColor);
-
- if ($this->transparent) {
- imagecolortransparent($image, $backColor);
- }
-
- $foreColor = imagecolorallocate($image,
- (int)($this->foreColor % 0x1000000 / 0x10000),
- (int)($this->foreColor % 0x10000 / 0x100),
- $this->foreColor % 0x100);
-
- $length = strlen($code);
- $box = imagettfbbox(30, 0, $this->fontFile, $code);
- $w = $box[4] - $box[0] + $this->offset * ($length - 1);
- $h = $box[1] - $box[5];
- $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
- $x = 10;
- $y = round($this->height * 27 / 40);
- for ($i = 0; $i < $length; ++$i) {
- $fontSize = (int)(rand(26, 32) * $scale * 0.8);
- $angle = rand(-10, 10);
- $letter = $code[$i];
- $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter);
- $x = $box[2] + $this->offset;
- }
-
- imagecolordeallocate($image, $foreColor);
-
- ob_start();
- imagepng($image);
- imagedestroy($image);
- return ob_get_clean();
- }
-
- /**
- * Renders the CAPTCHA image based on the code using ImageMagick library.
- * @param string $code the verification code
- * @return \Imagick image instance. Can be used as string. In this case it will contain image contents.
- */
- protected function renderImageByImagick($code)
- {
- $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . dechex($this->backColor));
- $foreColor = new \ImagickPixel('#' . dechex($this->foreColor));
-
- $image = new \Imagick();
- $image->newImage($this->width, $this->height, $backColor);
-
- $draw = new \ImagickDraw();
- $draw->setFont($this->fontFile);
- $draw->setFontSize(30);
- $fontMetrics = $image->queryFontMetrics($draw, $code);
-
- $length = strlen($code);
- $w = (int)($fontMetrics['textWidth']) - 8 + $this->offset * ($length - 1);
- $h = (int)($fontMetrics['textHeight']) - 8;
- $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
- $x = 10;
- $y = round($this->height * 27 / 40);
- for ($i = 0; $i < $length; ++$i) {
- $draw = new \ImagickDraw();
- $draw->setFont($this->fontFile);
- $draw->setFontSize((int)(rand(26, 32) * $scale * 0.8));
- $draw->setFillColor($foreColor);
- $image->annotateImage($draw, $x, $y, rand(-10, 10), $code[$i]);
- $fontMetrics = $image->queryFontMetrics($draw, $code[$i]);
- $x += (int)($fontMetrics['textWidth']) + $this->offset;
- }
-
- $image->setImageFormat('png');
- return $image;
- }
-
- /**
- * Sets the HTTP headers needed by image response.
- */
- protected function setHttpHeaders()
- {
- Yii::$app->getResponse()->getHeaders()
- ->set('Pragma', 'public')
- ->set('Expires', '0')
- ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
- ->set('Content-Transfer-Encoding', 'binary')
- ->set('Content-type', 'image/png');
- }
+ /**
+ * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated.
+ */
+ const REFRESH_GET_VAR = 'refresh';
+ /**
+ * @var integer how many times should the same CAPTCHA be displayed. Defaults to 3.
+ * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2).
+ */
+ public $testLimit = 3;
+ /**
+ * @var integer the width of the generated CAPTCHA image. Defaults to 120.
+ */
+ public $width = 120;
+ /**
+ * @var integer the height of the generated CAPTCHA image. Defaults to 50.
+ */
+ public $height = 50;
+ /**
+ * @var integer padding around the text. Defaults to 2.
+ */
+ public $padding = 2;
+ /**
+ * @var integer the background color. For example, 0x55FF00.
+ * Defaults to 0xFFFFFF, meaning white color.
+ */
+ public $backColor = 0xFFFFFF;
+ /**
+ * @var integer the font color. For example, 0x55FF00. Defaults to 0x2040A0 (blue color).
+ */
+ public $foreColor = 0x2040A0;
+ /**
+ * @var boolean whether to use transparent background. Defaults to false.
+ */
+ public $transparent = false;
+ /**
+ * @var integer the minimum length for randomly generated word. Defaults to 6.
+ */
+ public $minLength = 6;
+ /**
+ * @var integer the maximum length for randomly generated word. Defaults to 7.
+ */
+ public $maxLength = 7;
+ /**
+ * @var integer the offset between characters. Defaults to -2. You can adjust this property
+ * in order to decrease or increase the readability of the captcha.
+ **/
+ public $offset = -2;
+ /**
+ * @var string the TrueType font file. This can be either a file path or path alias.
+ */
+ public $fontFile = '@yii/captcha/SpicyRice.ttf';
+ /**
+ * @var string the fixed verification code. When this property is set,
+ * [[getVerifyCode()]] will always return the value of this property.
+ * This is mainly used in automated tests where we want to be able to reproduce
+ * the same verification code each time we run the tests.
+ * If not set, it means the verification code will be randomly generated.
+ */
+ public $fixedVerifyCode;
+
+ /**
+ * Initializes the action.
+ * @throws InvalidConfigException if the font file does not exist.
+ */
+ public function init()
+ {
+ $this->fontFile = Yii::getAlias($this->fontFile);
+ if (!is_file($this->fontFile)) {
+ throw new InvalidConfigException("The font file does not exist: {$this->fontFile}");
+ }
+ }
+
+ /**
+ * Runs the action.
+ */
+ public function run()
+ {
+ if (Yii::$app->request->getQueryParam(self::REFRESH_GET_VAR) !== null) {
+ // AJAX request for regenerating code
+ $code = $this->getVerifyCode(true);
+
+ return json_encode([
+ 'hash1' => $this->generateValidationHash($code),
+ 'hash2' => $this->generateValidationHash(strtolower($code)),
+ // we add a random 'v' parameter so that FireFox can refresh the image
+ // when src attribute of image tag is changed
+ 'url' => Url::to([$this->id, 'v' => uniqid()]),
+ ]);
+ } else {
+ $this->setHttpHeaders();
+
+ return $this->renderImage($this->getVerifyCode());
+ }
+ }
+
+ /**
+ * Generates a hash code that can be used for client side validation.
+ * @param string $code the CAPTCHA code
+ * @return string a hash code generated from the CAPTCHA code
+ */
+ public function generateValidationHash($code)
+ {
+ for ($h = 0, $i = strlen($code) - 1; $i >= 0; --$i) {
+ $h += ord($code[$i]);
+ }
+
+ return $h;
+ }
+
+ /**
+ * Gets the verification code.
+ * @param boolean $regenerate whether the verification code should be regenerated.
+ * @return string the verification code.
+ */
+ public function getVerifyCode($regenerate = false)
+ {
+ if ($this->fixedVerifyCode !== null) {
+ return $this->fixedVerifyCode;
+ }
+
+ $session = Yii::$app->getSession();
+ $session->open();
+ $name = $this->getSessionKey();
+ if ($session[$name] === null || $regenerate) {
+ $session[$name] = $this->generateVerifyCode();
+ $session[$name . 'count'] = 1;
+ }
+
+ return $session[$name];
+ }
+
+ /**
+ * Validates the input to see if it matches the generated code.
+ * @param string $input user input
+ * @param boolean $caseSensitive whether the comparison should be case-sensitive
+ * @return boolean whether the input is valid
+ */
+ public function validate($input, $caseSensitive)
+ {
+ $code = $this->getVerifyCode();
+ $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0;
+ $session = Yii::$app->getSession();
+ $session->open();
+ $name = $this->getSessionKey() . 'count';
+ $session[$name] = $session[$name] + 1;
+ if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) {
+ $this->getVerifyCode(true);
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Generates a new verification code.
+ * @return string the generated verification code
+ */
+ protected function generateVerifyCode()
+ {
+ if ($this->minLength > $this->maxLength) {
+ $this->maxLength = $this->minLength;
+ }
+ if ($this->minLength < 3) {
+ $this->minLength = 3;
+ }
+ if ($this->maxLength > 20) {
+ $this->maxLength = 20;
+ }
+ $length = mt_rand($this->minLength, $this->maxLength);
+
+ $letters = 'bcdfghjklmnpqrstvwxyz';
+ $vowels = 'aeiou';
+ $code = '';
+ for ($i = 0; $i < $length; ++$i) {
+ if ($i % 2 && mt_rand(0, 10) > 2 || !($i % 2) && mt_rand(0, 10) > 9) {
+ $code .= $vowels[mt_rand(0, 4)];
+ } else {
+ $code .= $letters[mt_rand(0, 20)];
+ }
+ }
+
+ return $code;
+ }
+
+ /**
+ * Returns the session variable name used to store verification code.
+ * @return string the session variable name
+ */
+ protected function getSessionKey()
+ {
+ return '__captcha/' . $this->getUniqueId();
+ }
+
+ /**
+ * Renders the CAPTCHA image.
+ * @param string $code the verification code
+ * @return string image contents
+ */
+ protected function renderImage($code)
+ {
+ if (Captcha::checkRequirements() === 'gd') {
+ return $this->renderImageByGD($code);
+ } else {
+ return $this->renderImageByImagick($code);
+ }
+ }
+
+ /**
+ * Renders the CAPTCHA image based on the code using GD library.
+ * @param string $code the verification code
+ * @return string image contents
+ */
+ protected function renderImageByGD($code)
+ {
+ $image = imagecreatetruecolor($this->width, $this->height);
+
+ $backColor = imagecolorallocate($image,
+ (int) ($this->backColor % 0x1000000 / 0x10000),
+ (int) ($this->backColor % 0x10000 / 0x100),
+ $this->backColor % 0x100);
+ imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backColor);
+ imagecolordeallocate($image, $backColor);
+
+ if ($this->transparent) {
+ imagecolortransparent($image, $backColor);
+ }
+
+ $foreColor = imagecolorallocate($image,
+ (int) ($this->foreColor % 0x1000000 / 0x10000),
+ (int) ($this->foreColor % 0x10000 / 0x100),
+ $this->foreColor % 0x100);
+
+ $length = strlen($code);
+ $box = imagettfbbox(30, 0, $this->fontFile, $code);
+ $w = $box[4] - $box[0] + $this->offset * ($length - 1);
+ $h = $box[1] - $box[5];
+ $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
+ $x = 10;
+ $y = round($this->height * 27 / 40);
+ for ($i = 0; $i < $length; ++$i) {
+ $fontSize = (int) (rand(26, 32) * $scale * 0.8);
+ $angle = rand(-10, 10);
+ $letter = $code[$i];
+ $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter);
+ $x = $box[2] + $this->offset;
+ }
+
+ imagecolordeallocate($image, $foreColor);
+
+ ob_start();
+ imagepng($image);
+ imagedestroy($image);
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Renders the CAPTCHA image based on the code using ImageMagick library.
+ * @param string $code the verification code
+ * @return \Imagick image instance. Can be used as string. In this case it will contain image contents.
+ */
+ protected function renderImageByImagick($code)
+ {
+ $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . dechex($this->backColor));
+ $foreColor = new \ImagickPixel('#' . dechex($this->foreColor));
+
+ $image = new \Imagick();
+ $image->newImage($this->width, $this->height, $backColor);
+
+ $draw = new \ImagickDraw();
+ $draw->setFont($this->fontFile);
+ $draw->setFontSize(30);
+ $fontMetrics = $image->queryFontMetrics($draw, $code);
+
+ $length = strlen($code);
+ $w = (int) ($fontMetrics['textWidth']) - 8 + $this->offset * ($length - 1);
+ $h = (int) ($fontMetrics['textHeight']) - 8;
+ $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
+ $x = 10;
+ $y = round($this->height * 27 / 40);
+ for ($i = 0; $i < $length; ++$i) {
+ $draw = new \ImagickDraw();
+ $draw->setFont($this->fontFile);
+ $draw->setFontSize((int) (rand(26, 32) * $scale * 0.8));
+ $draw->setFillColor($foreColor);
+ $image->annotateImage($draw, $x, $y, rand(-10, 10), $code[$i]);
+ $fontMetrics = $image->queryFontMetrics($draw, $code[$i]);
+ $x += (int) ($fontMetrics['textWidth']) + $this->offset;
+ }
+
+ $image->setImageFormat('png');
+
+ return $image;
+ }
+
+ /**
+ * Sets the HTTP headers needed by image response.
+ */
+ protected function setHttpHeaders()
+ {
+ Yii::$app->getResponse()->getHeaders()
+ ->set('Pragma', 'public')
+ ->set('Expires', '0')
+ ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+ ->set('Content-Transfer-Encoding', 'binary')
+ ->set('Content-type', 'image/png');
+ }
}
diff --git a/framework/captcha/CaptchaAsset.php b/framework/captcha/CaptchaAsset.php
index 4fc722f1da6..8af07e17a9f 100644
--- a/framework/captcha/CaptchaAsset.php
+++ b/framework/captcha/CaptchaAsset.php
@@ -17,11 +17,11 @@
*/
class CaptchaAsset extends AssetBundle
{
- public $sourcePath = '@yii/assets';
- public $js = [
- 'yii.captcha.js',
- ];
- public $depends = [
- 'yii\web\YiiAsset',
- ];
+ public $sourcePath = '@yii/assets';
+ public $js = [
+ 'yii.captcha.js',
+ ];
+ public $depends = [
+ 'yii\web\YiiAsset',
+ ];
}
diff --git a/framework/captcha/CaptchaValidator.php b/framework/captcha/CaptchaValidator.php
index acef79d8bb8..0be46d3be1f 100644
--- a/framework/captcha/CaptchaValidator.php
+++ b/framework/captcha/CaptchaValidator.php
@@ -26,81 +26,82 @@
*/
class CaptchaValidator extends Validator
{
- /**
- * @var boolean whether to skip this validator if the input is empty.
- */
- public $skipOnEmpty = false;
- /**
- * @var boolean whether the comparison is case sensitive. Defaults to false.
- */
- public $caseSensitive = false;
- /**
- * @var string the route of the controller action that renders the CAPTCHA image.
- */
- public $captchaAction = 'site/captcha';
+ /**
+ * @var boolean whether to skip this validator if the input is empty.
+ */
+ public $skipOnEmpty = false;
+ /**
+ * @var boolean whether the comparison is case sensitive. Defaults to false.
+ */
+ public $caseSensitive = false;
+ /**
+ * @var string the route of the controller action that renders the CAPTCHA image.
+ */
+ public $captchaAction = 'site/captcha';
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', 'The verification code is incorrect.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = Yii::t('yii', 'The verification code is incorrect.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ $captcha = $this->createCaptchaAction();
+ $valid = !is_array($value) && $captcha->validate($value, $this->caseSensitive);
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- $captcha = $this->createCaptchaAction();
- $valid = !is_array($value) && $captcha->validate($value, $this->caseSensitive);
- return $valid ? null : [$this->message, []];
- }
+ return $valid ? null : [$this->message, []];
+ }
- /**
- * Creates the CAPTCHA action object from the route specified by [[captchaAction]].
- * @return \yii\captcha\CaptchaAction the action object
- * @throws InvalidConfigException
- */
- public function createCaptchaAction()
- {
- $ca = Yii::$app->createController($this->captchaAction);
- if ($ca !== false) {
- /** @var \yii\base\Controller $controller */
- list($controller, $actionID) = $ca;
- $action = $controller->createAction($actionID);
- if ($action !== null) {
- return $action;
- }
- }
- throw new InvalidConfigException('Invalid CAPTCHA action ID: ' . $this->captchaAction);
- }
+ /**
+ * Creates the CAPTCHA action object from the route specified by [[captchaAction]].
+ * @return \yii\captcha\CaptchaAction the action object
+ * @throws InvalidConfigException
+ */
+ public function createCaptchaAction()
+ {
+ $ca = Yii::$app->createController($this->captchaAction);
+ if ($ca !== false) {
+ /** @var \yii\base\Controller $controller */
+ list($controller, $actionID) = $ca;
+ $action = $controller->createAction($actionID);
+ if ($action !== null) {
+ return $action;
+ }
+ }
+ throw new InvalidConfigException('Invalid CAPTCHA action ID: ' . $this->captchaAction);
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $captcha = $this->createCaptchaAction();
- $code = $captcha->getVerifyCode(false);
- $hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code));
- $options = [
- 'hash' => $hash,
- 'hashKey' => 'yiiCaptcha/' . $this->captchaAction,
- 'caseSensitive' => $this->caseSensitive,
- 'message' => strtr($this->message, [
- 'attribute' => $object->getAttributeLabel($attribute),
- ]),
- ];
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $captcha = $this->createCaptchaAction();
+ $code = $captcha->getVerifyCode(false);
+ $hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code));
+ $options = [
+ 'hash' => $hash,
+ 'hashKey' => 'yiiCaptcha/' . $this->captchaAction,
+ 'caseSensitive' => $this->caseSensitive,
+ 'message' => strtr($this->message, [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ ]),
+ ];
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
- ValidationAsset::register($view);
- return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');';
- }
+ ValidationAsset::register($view);
+
+ return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');';
+ }
}
diff --git a/framework/classes.php b/framework/classes.php
index 552b943f68b..3589a8ca30a 100644
--- a/framework/classes.php
+++ b/framework/classes.php
@@ -11,265 +11,265 @@
*/
return [
- 'yii\base\Action' => YII_PATH . '/base/Action.php',
- 'yii\base\ActionEvent' => YII_PATH . '/base/ActionEvent.php',
- 'yii\base\ActionFilter' => YII_PATH . '/base/ActionFilter.php',
- 'yii\base\Application' => YII_PATH . '/base/Application.php',
- 'yii\base\ArrayAccessTrait' => YII_PATH . '/base/ArrayAccessTrait.php',
- 'yii\base\Arrayable' => YII_PATH . '/base/Arrayable.php',
- 'yii\base\Behavior' => YII_PATH . '/base/Behavior.php',
- 'yii\base\Component' => YII_PATH . '/base/Component.php',
- 'yii\base\Controller' => YII_PATH . '/base/Controller.php',
- 'yii\base\DynamicModel' => YII_PATH . '/base/DynamicModel.php',
- 'yii\base\ErrorException' => YII_PATH . '/base/ErrorException.php',
- 'yii\base\ErrorHandler' => YII_PATH . '/base/ErrorHandler.php',
- 'yii\base\Event' => YII_PATH . '/base/Event.php',
- 'yii\base\Exception' => YII_PATH . '/base/Exception.php',
- 'yii\base\Extension' => YII_PATH . '/base/Extension.php',
- 'yii\base\Formatter' => YII_PATH . '/base/Formatter.php',
- 'yii\base\InlineAction' => YII_PATH . '/base/InlineAction.php',
- 'yii\base\InvalidCallException' => YII_PATH . '/base/InvalidCallException.php',
- 'yii\base\InvalidConfigException' => YII_PATH . '/base/InvalidConfigException.php',
- 'yii\base\InvalidParamException' => YII_PATH . '/base/InvalidParamException.php',
- 'yii\base\InvalidRouteException' => YII_PATH . '/base/InvalidRouteException.php',
- 'yii\base\MailEvent' => YII_PATH . '/base/MailEvent.php',
- 'yii\base\Model' => YII_PATH . '/base/Model.php',
- 'yii\base\ModelEvent' => YII_PATH . '/base/ModelEvent.php',
- 'yii\base\Module' => YII_PATH . '/base/Module.php',
- 'yii\base\NotSupportedException' => YII_PATH . '/base/NotSupportedException.php',
- 'yii\base\Object' => YII_PATH . '/base/Object.php',
- 'yii\base\Request' => YII_PATH . '/base/Request.php',
- 'yii\base\Response' => YII_PATH . '/base/Response.php',
- 'yii\base\Theme' => YII_PATH . '/base/Theme.php',
- 'yii\base\UnknownClassException' => YII_PATH . '/base/UnknownClassException.php',
- 'yii\base\UnknownMethodException' => YII_PATH . '/base/UnknownMethodException.php',
- 'yii\base\UnknownPropertyException' => YII_PATH . '/base/UnknownPropertyException.php',
- 'yii\base\UserException' => YII_PATH . '/base/UserException.php',
- 'yii\base\View' => YII_PATH . '/base/View.php',
- 'yii\base\ViewContextInterface' => YII_PATH . '/base/ViewContextInterface.php',
- 'yii\base\ViewEvent' => YII_PATH . '/base/ViewEvent.php',
- 'yii\base\ViewRenderer' => YII_PATH . '/base/ViewRenderer.php',
- 'yii\base\Widget' => YII_PATH . '/base/Widget.php',
- 'yii\behaviors\AttributeBehavior' => YII_PATH . '/behaviors/AttributeBehavior.php',
- 'yii\behaviors\BlameableBehavior' => YII_PATH . '/behaviors/BlameableBehavior.php',
- 'yii\behaviors\TimestampBehavior' => YII_PATH . '/behaviors/TimestampBehavior.php',
- 'yii\caching\ApcCache' => YII_PATH . '/caching/ApcCache.php',
- 'yii\caching\Cache' => YII_PATH . '/caching/Cache.php',
- 'yii\caching\ChainedDependency' => YII_PATH . '/caching/ChainedDependency.php',
- 'yii\caching\DbCache' => YII_PATH . '/caching/DbCache.php',
- 'yii\caching\DbDependency' => YII_PATH . '/caching/DbDependency.php',
- 'yii\caching\Dependency' => YII_PATH . '/caching/Dependency.php',
- 'yii\caching\DummyCache' => YII_PATH . '/caching/DummyCache.php',
- 'yii\caching\ExpressionDependency' => YII_PATH . '/caching/ExpressionDependency.php',
- 'yii\caching\FileCache' => YII_PATH . '/caching/FileCache.php',
- 'yii\caching\FileDependency' => YII_PATH . '/caching/FileDependency.php',
- 'yii\caching\GroupDependency' => YII_PATH . '/caching/GroupDependency.php',
- 'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php',
- 'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php',
- 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php',
- 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php',
- 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php',
- 'yii\captcha\Captcha' => YII_PATH . '/captcha/Captcha.php',
- 'yii\captcha\CaptchaAction' => YII_PATH . '/captcha/CaptchaAction.php',
- 'yii\captcha\CaptchaAsset' => YII_PATH . '/captcha/CaptchaAsset.php',
- 'yii\captcha\CaptchaValidator' => YII_PATH . '/captcha/CaptchaValidator.php',
- 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php',
- 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php',
- 'yii\data\BaseDataProvider' => YII_PATH . '/data/BaseDataProvider.php',
- 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php',
- 'yii\data\ModelSerializer' => YII_PATH . '/data/ModelSerializer.php',
- 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php',
- 'yii\data\Sort' => YII_PATH . '/data/Sort.php',
- 'yii\data\SqlDataProvider' => YII_PATH . '/data/SqlDataProvider.php',
- 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php',
- 'yii\db\ActiveQueryInterface' => YII_PATH . '/db/ActiveQueryInterface.php',
- 'yii\db\ActiveQueryTrait' => YII_PATH . '/db/ActiveQueryTrait.php',
- 'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php',
- 'yii\db\ActiveRecordInterface' => YII_PATH . '/db/ActiveRecordInterface.php',
- 'yii\db\ActiveRelation' => YII_PATH . '/db/ActiveRelation.php',
- 'yii\db\ActiveRelationInterface' => YII_PATH . '/db/ActiveRelationInterface.php',
- 'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php',
- 'yii\db\BaseActiveRecord' => YII_PATH . '/db/BaseActiveRecord.php',
- 'yii\db\BatchQueryResult' => YII_PATH . '/db/BatchQueryResult.php',
- 'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php',
- 'yii\db\Command' => YII_PATH . '/db/Command.php',
- 'yii\db\Connection' => YII_PATH . '/db/Connection.php',
- 'yii\db\DataReader' => YII_PATH . '/db/DataReader.php',
- 'yii\db\Exception' => YII_PATH . '/db/Exception.php',
- 'yii\db\Expression' => YII_PATH . '/db/Expression.php',
- 'yii\db\Migration' => YII_PATH . '/db/Migration.php',
- 'yii\db\Query' => YII_PATH . '/db/Query.php',
- 'yii\db\QueryBuilder' => YII_PATH . '/db/QueryBuilder.php',
- 'yii\db\QueryInterface' => YII_PATH . '/db/QueryInterface.php',
- 'yii\db\QueryTrait' => YII_PATH . '/db/QueryTrait.php',
- 'yii\db\Schema' => YII_PATH . '/db/Schema.php',
- 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php',
- 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php',
- 'yii\db\Transaction' => YII_PATH . '/db/Transaction.php',
- 'yii\db\cubrid\QueryBuilder' => YII_PATH . '/db/cubrid/QueryBuilder.php',
- 'yii\db\cubrid\Schema' => YII_PATH . '/db/cubrid/Schema.php',
- 'yii\db\mssql\PDO' => YII_PATH . '/db/mssql/PDO.php',
- 'yii\db\mssql\QueryBuilder' => YII_PATH . '/db/mssql/QueryBuilder.php',
- 'yii\db\mssql\Schema' => YII_PATH . '/db/mssql/Schema.php',
- 'yii\db\mssql\SqlsrvPDO' => YII_PATH . '/db/mssql/SqlsrvPDO.php',
- 'yii\db\mssql\TableSchema' => YII_PATH . '/db/mssql/TableSchema.php',
- 'yii\db\mysql\QueryBuilder' => YII_PATH . '/db/mysql/QueryBuilder.php',
- 'yii\db\mysql\Schema' => YII_PATH . '/db/mysql/Schema.php',
- 'yii\db\oci\QueryBuilder' => YII_PATH . '/db/oci/QueryBuilder.php',
- 'yii\db\oci\Schema' => YII_PATH . '/db/oci/Schema.php',
- 'yii\db\pgsql\QueryBuilder' => YII_PATH . '/db/pgsql/QueryBuilder.php',
- 'yii\db\pgsql\Schema' => YII_PATH . '/db/pgsql/Schema.php',
- 'yii\db\sqlite\QueryBuilder' => YII_PATH . '/db/sqlite/QueryBuilder.php',
- 'yii\db\sqlite\Schema' => YII_PATH . '/db/sqlite/Schema.php',
- 'yii\grid\ActionColumn' => YII_PATH . '/grid/ActionColumn.php',
- 'yii\grid\CheckboxColumn' => YII_PATH . '/grid/CheckboxColumn.php',
- 'yii\grid\Column' => YII_PATH . '/grid/Column.php',
- 'yii\grid\DataColumn' => YII_PATH . '/grid/DataColumn.php',
- 'yii\grid\GridView' => YII_PATH . '/grid/GridView.php',
- 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php',
- 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php',
- 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php',
- 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php',
- 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php',
- 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php',
- 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php',
- 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php',
- 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php',
- 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php',
- 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php',
- 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php',
- 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php',
- 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php',
- 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php',
- 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php',
- 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php',
- 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php',
- 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php',
- 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php',
- 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php',
- 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php',
- 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php',
- 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php',
- 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php',
- 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php',
- 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php',
- 'yii\i18n\GettextMessageSource' => YII_PATH . '/i18n/GettextMessageSource.php',
- 'yii\i18n\GettextMoFile' => YII_PATH . '/i18n/GettextMoFile.php',
- 'yii\i18n\GettextPoFile' => YII_PATH . '/i18n/GettextPoFile.php',
- 'yii\i18n\I18N' => YII_PATH . '/i18n/I18N.php',
- 'yii\i18n\MessageFormatter' => YII_PATH . '/i18n/MessageFormatter.php',
- 'yii\i18n\MessageSource' => YII_PATH . '/i18n/MessageSource.php',
- 'yii\i18n\MissingTranslationEvent' => YII_PATH . '/i18n/MissingTranslationEvent.php',
- 'yii\i18n\PhpMessageSource' => YII_PATH . '/i18n/PhpMessageSource.php',
- 'yii\log\DbTarget' => YII_PATH . '/log/DbTarget.php',
- 'yii\log\EmailTarget' => YII_PATH . '/log/EmailTarget.php',
- 'yii\log\FileTarget' => YII_PATH . '/log/FileTarget.php',
- 'yii\log\Logger' => YII_PATH . '/log/Logger.php',
- 'yii\log\Target' => YII_PATH . '/log/Target.php',
- 'yii\mail\BaseMailer' => YII_PATH . '/mail/BaseMailer.php',
- 'yii\mail\BaseMessage' => YII_PATH . '/mail/BaseMessage.php',
- 'yii\mail\MailerInterface' => YII_PATH . '/mail/MailerInterface.php',
- 'yii\mail\MessageInterface' => YII_PATH . '/mail/MessageInterface.php',
- 'yii\mutex\DbMutex' => YII_PATH . '/mutex/DbMutex.php',
- 'yii\mutex\FileMutex' => YII_PATH . '/mutex/FileMutex.php',
- 'yii\mutex\Mutex' => YII_PATH . '/mutex/Mutex.php',
- 'yii\mutex\MysqlMutex' => YII_PATH . '/mutex/MysqlMutex.php',
- 'yii\rbac\Assignment' => YII_PATH . '/rbac/Assignment.php',
- 'yii\rbac\DbManager' => YII_PATH . '/rbac/DbManager.php',
- 'yii\rbac\Item' => YII_PATH . '/rbac/Item.php',
- 'yii\rbac\Manager' => YII_PATH . '/rbac/Manager.php',
- 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php',
- 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php',
- 'yii\test\ActiveFixture' => YII_PATH . '/test/ActiveFixture.php',
- 'yii\test\BaseActiveFixture' => YII_PATH . '/test/BaseActiveFixture.php',
- 'yii\test\DbFixture' => YII_PATH . '/test/DbFixture.php',
- 'yii\test\Fixture' => YII_PATH . '/test/Fixture.php',
- 'yii\test\FixtureTrait' => YII_PATH . '/test/FixtureTrait.php',
- 'yii\test\InitDbFixture' => YII_PATH . '/test/InitDbFixture.php',
- 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php',
- 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php',
- 'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php',
- 'yii\validators\DefaultValueValidator' => YII_PATH . '/validators/DefaultValueValidator.php',
- 'yii\validators\EmailValidator' => YII_PATH . '/validators/EmailValidator.php',
- 'yii\validators\ExistValidator' => YII_PATH . '/validators/ExistValidator.php',
- 'yii\validators\FileValidator' => YII_PATH . '/validators/FileValidator.php',
- 'yii\validators\FilterValidator' => YII_PATH . '/validators/FilterValidator.php',
- 'yii\validators\ImageValidator' => YII_PATH . '/validators/ImageValidator.php',
- 'yii\validators\InlineValidator' => YII_PATH . '/validators/InlineValidator.php',
- 'yii\validators\NumberValidator' => YII_PATH . '/validators/NumberValidator.php',
- 'yii\validators\PunycodeAsset' => YII_PATH . '/validators/PunycodeAsset.php',
- 'yii\validators\RangeValidator' => YII_PATH . '/validators/RangeValidator.php',
- 'yii\validators\RegularExpressionValidator' => YII_PATH . '/validators/RegularExpressionValidator.php',
- 'yii\validators\RequiredValidator' => YII_PATH . '/validators/RequiredValidator.php',
- 'yii\validators\SafeValidator' => YII_PATH . '/validators/SafeValidator.php',
- 'yii\validators\StringValidator' => YII_PATH . '/validators/StringValidator.php',
- 'yii\validators\UniqueValidator' => YII_PATH . '/validators/UniqueValidator.php',
- 'yii\validators\UrlValidator' => YII_PATH . '/validators/UrlValidator.php',
- 'yii\validators\ValidationAsset' => YII_PATH . '/validators/ValidationAsset.php',
- 'yii\validators\Validator' => YII_PATH . '/validators/Validator.php',
- 'yii\web\AccessControl' => YII_PATH . '/web/AccessControl.php',
- 'yii\web\AccessRule' => YII_PATH . '/web/AccessRule.php',
- 'yii\web\Application' => YII_PATH . '/web/Application.php',
- 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php',
- 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php',
- 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php',
- 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php',
- 'yii\web\BadRequestHttpException' => YII_PATH . '/web/BadRequestHttpException.php',
- 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php',
- 'yii\web\ConflictHttpException' => YII_PATH . '/web/ConflictHttpException.php',
- 'yii\web\Controller' => YII_PATH . '/web/Controller.php',
- 'yii\web\Cookie' => YII_PATH . '/web/Cookie.php',
- 'yii\web\CookieCollection' => YII_PATH . '/web/CookieCollection.php',
- 'yii\web\DbSession' => YII_PATH . '/web/DbSession.php',
- 'yii\web\ErrorAction' => YII_PATH . '/web/ErrorAction.php',
- 'yii\web\ForbiddenHttpException' => YII_PATH . '/web/ForbiddenHttpException.php',
- 'yii\web\GoneHttpException' => YII_PATH . '/web/GoneHttpException.php',
- 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php',
- 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php',
- 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php',
- 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php',
- 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php',
- 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php',
- 'yii\web\JsonParser' => YII_PATH . '/web/JsonParser.php',
- 'yii\web\MethodNotAllowedHttpException' => YII_PATH . '/web/MethodNotAllowedHttpException.php',
- 'yii\web\NotAcceptableHttpException' => YII_PATH . '/web/NotAcceptableHttpException.php',
- 'yii\web\NotFoundHttpException' => YII_PATH . '/web/NotFoundHttpException.php',
- 'yii\web\PageCache' => YII_PATH . '/web/PageCache.php',
- 'yii\web\Request' => YII_PATH . '/web/Request.php',
- 'yii\web\RequestParserInterface' => YII_PATH . '/web/RequestParserInterface.php',
- 'yii\web\Response' => YII_PATH . '/web/Response.php',
- 'yii\web\ResponseFormatterInterface' => YII_PATH . '/web/ResponseFormatterInterface.php',
- 'yii\web\Session' => YII_PATH . '/web/Session.php',
- 'yii\web\SessionIterator' => YII_PATH . '/web/SessionIterator.php',
- 'yii\web\TooManyRequestsHttpException' => YII_PATH . '/web/TooManyRequestsHttpException.php',
- 'yii\web\UnauthorizedHttpException' => YII_PATH . '/web/UnauthorizedHttpException.php',
- 'yii\web\UnsupportedMediaTypeHttpException' => YII_PATH . '/web/UnsupportedMediaTypeHttpException.php',
- 'yii\web\UploadedFile' => YII_PATH . '/web/UploadedFile.php',
- 'yii\web\UrlManager' => YII_PATH . '/web/UrlManager.php',
- 'yii\web\UrlRule' => YII_PATH . '/web/UrlRule.php',
- 'yii\web\User' => YII_PATH . '/web/User.php',
- 'yii\web\UserEvent' => YII_PATH . '/web/UserEvent.php',
- 'yii\web\VerbFilter' => YII_PATH . '/web/VerbFilter.php',
- 'yii\web\View' => YII_PATH . '/web/View.php',
- 'yii\web\XmlResponseFormatter' => YII_PATH . '/web/XmlResponseFormatter.php',
- 'yii\web\YiiAsset' => YII_PATH . '/web/YiiAsset.php',
- 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php',
- 'yii\widgets\ActiveForm' => YII_PATH . '/widgets/ActiveForm.php',
- 'yii\widgets\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php',
- 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php',
- 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php',
- 'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.php',
- 'yii\widgets\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php',
- 'yii\widgets\DetailView' => YII_PATH . '/widgets/DetailView.php',
- 'yii\widgets\FragmentCache' => YII_PATH . '/widgets/FragmentCache.php',
- 'yii\widgets\InputWidget' => YII_PATH . '/widgets/InputWidget.php',
- 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php',
- 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php',
- 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php',
- 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php',
- 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php',
- 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php',
- 'yii\widgets\Pjax' => YII_PATH . '/widgets/Pjax.php',
- 'yii\widgets\PjaxAsset' => YII_PATH . '/widgets/PjaxAsset.php',
- 'yii\widgets\Spaceless' => YII_PATH . '/widgets/Spaceless.php',
+ 'yii\base\Action' => YII_PATH . '/base/Action.php',
+ 'yii\base\ActionEvent' => YII_PATH . '/base/ActionEvent.php',
+ 'yii\base\ActionFilter' => YII_PATH . '/base/ActionFilter.php',
+ 'yii\base\Application' => YII_PATH . '/base/Application.php',
+ 'yii\base\ArrayAccessTrait' => YII_PATH . '/base/ArrayAccessTrait.php',
+ 'yii\base\Arrayable' => YII_PATH . '/base/Arrayable.php',
+ 'yii\base\Behavior' => YII_PATH . '/base/Behavior.php',
+ 'yii\base\Component' => YII_PATH . '/base/Component.php',
+ 'yii\base\Controller' => YII_PATH . '/base/Controller.php',
+ 'yii\base\DynamicModel' => YII_PATH . '/base/DynamicModel.php',
+ 'yii\base\ErrorException' => YII_PATH . '/base/ErrorException.php',
+ 'yii\base\ErrorHandler' => YII_PATH . '/base/ErrorHandler.php',
+ 'yii\base\Event' => YII_PATH . '/base/Event.php',
+ 'yii\base\Exception' => YII_PATH . '/base/Exception.php',
+ 'yii\base\Extension' => YII_PATH . '/base/Extension.php',
+ 'yii\base\Formatter' => YII_PATH . '/base/Formatter.php',
+ 'yii\base\InlineAction' => YII_PATH . '/base/InlineAction.php',
+ 'yii\base\InvalidCallException' => YII_PATH . '/base/InvalidCallException.php',
+ 'yii\base\InvalidConfigException' => YII_PATH . '/base/InvalidConfigException.php',
+ 'yii\base\InvalidParamException' => YII_PATH . '/base/InvalidParamException.php',
+ 'yii\base\InvalidRouteException' => YII_PATH . '/base/InvalidRouteException.php',
+ 'yii\base\MailEvent' => YII_PATH . '/base/MailEvent.php',
+ 'yii\base\Model' => YII_PATH . '/base/Model.php',
+ 'yii\base\ModelEvent' => YII_PATH . '/base/ModelEvent.php',
+ 'yii\base\Module' => YII_PATH . '/base/Module.php',
+ 'yii\base\NotSupportedException' => YII_PATH . '/base/NotSupportedException.php',
+ 'yii\base\Object' => YII_PATH . '/base/Object.php',
+ 'yii\base\Request' => YII_PATH . '/base/Request.php',
+ 'yii\base\Response' => YII_PATH . '/base/Response.php',
+ 'yii\base\Theme' => YII_PATH . '/base/Theme.php',
+ 'yii\base\UnknownClassException' => YII_PATH . '/base/UnknownClassException.php',
+ 'yii\base\UnknownMethodException' => YII_PATH . '/base/UnknownMethodException.php',
+ 'yii\base\UnknownPropertyException' => YII_PATH . '/base/UnknownPropertyException.php',
+ 'yii\base\UserException' => YII_PATH . '/base/UserException.php',
+ 'yii\base\View' => YII_PATH . '/base/View.php',
+ 'yii\base\ViewContextInterface' => YII_PATH . '/base/ViewContextInterface.php',
+ 'yii\base\ViewEvent' => YII_PATH . '/base/ViewEvent.php',
+ 'yii\base\ViewRenderer' => YII_PATH . '/base/ViewRenderer.php',
+ 'yii\base\Widget' => YII_PATH . '/base/Widget.php',
+ 'yii\behaviors\AttributeBehavior' => YII_PATH . '/behaviors/AttributeBehavior.php',
+ 'yii\behaviors\BlameableBehavior' => YII_PATH . '/behaviors/BlameableBehavior.php',
+ 'yii\behaviors\TimestampBehavior' => YII_PATH . '/behaviors/TimestampBehavior.php',
+ 'yii\caching\ApcCache' => YII_PATH . '/caching/ApcCache.php',
+ 'yii\caching\Cache' => YII_PATH . '/caching/Cache.php',
+ 'yii\caching\ChainedDependency' => YII_PATH . '/caching/ChainedDependency.php',
+ 'yii\caching\DbCache' => YII_PATH . '/caching/DbCache.php',
+ 'yii\caching\DbDependency' => YII_PATH . '/caching/DbDependency.php',
+ 'yii\caching\Dependency' => YII_PATH . '/caching/Dependency.php',
+ 'yii\caching\DummyCache' => YII_PATH . '/caching/DummyCache.php',
+ 'yii\caching\ExpressionDependency' => YII_PATH . '/caching/ExpressionDependency.php',
+ 'yii\caching\FileCache' => YII_PATH . '/caching/FileCache.php',
+ 'yii\caching\FileDependency' => YII_PATH . '/caching/FileDependency.php',
+ 'yii\caching\GroupDependency' => YII_PATH . '/caching/GroupDependency.php',
+ 'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php',
+ 'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php',
+ 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php',
+ 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php',
+ 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php',
+ 'yii\captcha\Captcha' => YII_PATH . '/captcha/Captcha.php',
+ 'yii\captcha\CaptchaAction' => YII_PATH . '/captcha/CaptchaAction.php',
+ 'yii\captcha\CaptchaAsset' => YII_PATH . '/captcha/CaptchaAsset.php',
+ 'yii\captcha\CaptchaValidator' => YII_PATH . '/captcha/CaptchaValidator.php',
+ 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php',
+ 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php',
+ 'yii\data\BaseDataProvider' => YII_PATH . '/data/BaseDataProvider.php',
+ 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php',
+ 'yii\data\ModelSerializer' => YII_PATH . '/data/ModelSerializer.php',
+ 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php',
+ 'yii\data\Sort' => YII_PATH . '/data/Sort.php',
+ 'yii\data\SqlDataProvider' => YII_PATH . '/data/SqlDataProvider.php',
+ 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php',
+ 'yii\db\ActiveQueryInterface' => YII_PATH . '/db/ActiveQueryInterface.php',
+ 'yii\db\ActiveQueryTrait' => YII_PATH . '/db/ActiveQueryTrait.php',
+ 'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php',
+ 'yii\db\ActiveRecordInterface' => YII_PATH . '/db/ActiveRecordInterface.php',
+ 'yii\db\ActiveRelation' => YII_PATH . '/db/ActiveRelation.php',
+ 'yii\db\ActiveRelationInterface' => YII_PATH . '/db/ActiveRelationInterface.php',
+ 'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php',
+ 'yii\db\BaseActiveRecord' => YII_PATH . '/db/BaseActiveRecord.php',
+ 'yii\db\BatchQueryResult' => YII_PATH . '/db/BatchQueryResult.php',
+ 'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php',
+ 'yii\db\Command' => YII_PATH . '/db/Command.php',
+ 'yii\db\Connection' => YII_PATH . '/db/Connection.php',
+ 'yii\db\DataReader' => YII_PATH . '/db/DataReader.php',
+ 'yii\db\Exception' => YII_PATH . '/db/Exception.php',
+ 'yii\db\Expression' => YII_PATH . '/db/Expression.php',
+ 'yii\db\Migration' => YII_PATH . '/db/Migration.php',
+ 'yii\db\Query' => YII_PATH . '/db/Query.php',
+ 'yii\db\QueryBuilder' => YII_PATH . '/db/QueryBuilder.php',
+ 'yii\db\QueryInterface' => YII_PATH . '/db/QueryInterface.php',
+ 'yii\db\QueryTrait' => YII_PATH . '/db/QueryTrait.php',
+ 'yii\db\Schema' => YII_PATH . '/db/Schema.php',
+ 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php',
+ 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php',
+ 'yii\db\Transaction' => YII_PATH . '/db/Transaction.php',
+ 'yii\db\cubrid\QueryBuilder' => YII_PATH . '/db/cubrid/QueryBuilder.php',
+ 'yii\db\cubrid\Schema' => YII_PATH . '/db/cubrid/Schema.php',
+ 'yii\db\mssql\PDO' => YII_PATH . '/db/mssql/PDO.php',
+ 'yii\db\mssql\QueryBuilder' => YII_PATH . '/db/mssql/QueryBuilder.php',
+ 'yii\db\mssql\Schema' => YII_PATH . '/db/mssql/Schema.php',
+ 'yii\db\mssql\SqlsrvPDO' => YII_PATH . '/db/mssql/SqlsrvPDO.php',
+ 'yii\db\mssql\TableSchema' => YII_PATH . '/db/mssql/TableSchema.php',
+ 'yii\db\mysql\QueryBuilder' => YII_PATH . '/db/mysql/QueryBuilder.php',
+ 'yii\db\mysql\Schema' => YII_PATH . '/db/mysql/Schema.php',
+ 'yii\db\oci\QueryBuilder' => YII_PATH . '/db/oci/QueryBuilder.php',
+ 'yii\db\oci\Schema' => YII_PATH . '/db/oci/Schema.php',
+ 'yii\db\pgsql\QueryBuilder' => YII_PATH . '/db/pgsql/QueryBuilder.php',
+ 'yii\db\pgsql\Schema' => YII_PATH . '/db/pgsql/Schema.php',
+ 'yii\db\sqlite\QueryBuilder' => YII_PATH . '/db/sqlite/QueryBuilder.php',
+ 'yii\db\sqlite\Schema' => YII_PATH . '/db/sqlite/Schema.php',
+ 'yii\grid\ActionColumn' => YII_PATH . '/grid/ActionColumn.php',
+ 'yii\grid\CheckboxColumn' => YII_PATH . '/grid/CheckboxColumn.php',
+ 'yii\grid\Column' => YII_PATH . '/grid/Column.php',
+ 'yii\grid\DataColumn' => YII_PATH . '/grid/DataColumn.php',
+ 'yii\grid\GridView' => YII_PATH . '/grid/GridView.php',
+ 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php',
+ 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php',
+ 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php',
+ 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php',
+ 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php',
+ 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php',
+ 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php',
+ 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php',
+ 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php',
+ 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php',
+ 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php',
+ 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php',
+ 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php',
+ 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php',
+ 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php',
+ 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php',
+ 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php',
+ 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php',
+ 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php',
+ 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php',
+ 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php',
+ 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php',
+ 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php',
+ 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php',
+ 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php',
+ 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php',
+ 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php',
+ 'yii\i18n\GettextMessageSource' => YII_PATH . '/i18n/GettextMessageSource.php',
+ 'yii\i18n\GettextMoFile' => YII_PATH . '/i18n/GettextMoFile.php',
+ 'yii\i18n\GettextPoFile' => YII_PATH . '/i18n/GettextPoFile.php',
+ 'yii\i18n\I18N' => YII_PATH . '/i18n/I18N.php',
+ 'yii\i18n\MessageFormatter' => YII_PATH . '/i18n/MessageFormatter.php',
+ 'yii\i18n\MessageSource' => YII_PATH . '/i18n/MessageSource.php',
+ 'yii\i18n\MissingTranslationEvent' => YII_PATH . '/i18n/MissingTranslationEvent.php',
+ 'yii\i18n\PhpMessageSource' => YII_PATH . '/i18n/PhpMessageSource.php',
+ 'yii\log\DbTarget' => YII_PATH . '/log/DbTarget.php',
+ 'yii\log\EmailTarget' => YII_PATH . '/log/EmailTarget.php',
+ 'yii\log\FileTarget' => YII_PATH . '/log/FileTarget.php',
+ 'yii\log\Logger' => YII_PATH . '/log/Logger.php',
+ 'yii\log\Target' => YII_PATH . '/log/Target.php',
+ 'yii\mail\BaseMailer' => YII_PATH . '/mail/BaseMailer.php',
+ 'yii\mail\BaseMessage' => YII_PATH . '/mail/BaseMessage.php',
+ 'yii\mail\MailerInterface' => YII_PATH . '/mail/MailerInterface.php',
+ 'yii\mail\MessageInterface' => YII_PATH . '/mail/MessageInterface.php',
+ 'yii\mutex\DbMutex' => YII_PATH . '/mutex/DbMutex.php',
+ 'yii\mutex\FileMutex' => YII_PATH . '/mutex/FileMutex.php',
+ 'yii\mutex\Mutex' => YII_PATH . '/mutex/Mutex.php',
+ 'yii\mutex\MysqlMutex' => YII_PATH . '/mutex/MysqlMutex.php',
+ 'yii\rbac\Assignment' => YII_PATH . '/rbac/Assignment.php',
+ 'yii\rbac\DbManager' => YII_PATH . '/rbac/DbManager.php',
+ 'yii\rbac\Item' => YII_PATH . '/rbac/Item.php',
+ 'yii\rbac\Manager' => YII_PATH . '/rbac/Manager.php',
+ 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php',
+ 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php',
+ 'yii\test\ActiveFixture' => YII_PATH . '/test/ActiveFixture.php',
+ 'yii\test\BaseActiveFixture' => YII_PATH . '/test/BaseActiveFixture.php',
+ 'yii\test\DbFixture' => YII_PATH . '/test/DbFixture.php',
+ 'yii\test\Fixture' => YII_PATH . '/test/Fixture.php',
+ 'yii\test\FixtureTrait' => YII_PATH . '/test/FixtureTrait.php',
+ 'yii\test\InitDbFixture' => YII_PATH . '/test/InitDbFixture.php',
+ 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php',
+ 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php',
+ 'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php',
+ 'yii\validators\DefaultValueValidator' => YII_PATH . '/validators/DefaultValueValidator.php',
+ 'yii\validators\EmailValidator' => YII_PATH . '/validators/EmailValidator.php',
+ 'yii\validators\ExistValidator' => YII_PATH . '/validators/ExistValidator.php',
+ 'yii\validators\FileValidator' => YII_PATH . '/validators/FileValidator.php',
+ 'yii\validators\FilterValidator' => YII_PATH . '/validators/FilterValidator.php',
+ 'yii\validators\ImageValidator' => YII_PATH . '/validators/ImageValidator.php',
+ 'yii\validators\InlineValidator' => YII_PATH . '/validators/InlineValidator.php',
+ 'yii\validators\NumberValidator' => YII_PATH . '/validators/NumberValidator.php',
+ 'yii\validators\PunycodeAsset' => YII_PATH . '/validators/PunycodeAsset.php',
+ 'yii\validators\RangeValidator' => YII_PATH . '/validators/RangeValidator.php',
+ 'yii\validators\RegularExpressionValidator' => YII_PATH . '/validators/RegularExpressionValidator.php',
+ 'yii\validators\RequiredValidator' => YII_PATH . '/validators/RequiredValidator.php',
+ 'yii\validators\SafeValidator' => YII_PATH . '/validators/SafeValidator.php',
+ 'yii\validators\StringValidator' => YII_PATH . '/validators/StringValidator.php',
+ 'yii\validators\UniqueValidator' => YII_PATH . '/validators/UniqueValidator.php',
+ 'yii\validators\UrlValidator' => YII_PATH . '/validators/UrlValidator.php',
+ 'yii\validators\ValidationAsset' => YII_PATH . '/validators/ValidationAsset.php',
+ 'yii\validators\Validator' => YII_PATH . '/validators/Validator.php',
+ 'yii\web\AccessControl' => YII_PATH . '/web/AccessControl.php',
+ 'yii\web\AccessRule' => YII_PATH . '/web/AccessRule.php',
+ 'yii\web\Application' => YII_PATH . '/web/Application.php',
+ 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php',
+ 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php',
+ 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php',
+ 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php',
+ 'yii\web\BadRequestHttpException' => YII_PATH . '/web/BadRequestHttpException.php',
+ 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php',
+ 'yii\web\ConflictHttpException' => YII_PATH . '/web/ConflictHttpException.php',
+ 'yii\web\Controller' => YII_PATH . '/web/Controller.php',
+ 'yii\web\Cookie' => YII_PATH . '/web/Cookie.php',
+ 'yii\web\CookieCollection' => YII_PATH . '/web/CookieCollection.php',
+ 'yii\web\DbSession' => YII_PATH . '/web/DbSession.php',
+ 'yii\web\ErrorAction' => YII_PATH . '/web/ErrorAction.php',
+ 'yii\web\ForbiddenHttpException' => YII_PATH . '/web/ForbiddenHttpException.php',
+ 'yii\web\GoneHttpException' => YII_PATH . '/web/GoneHttpException.php',
+ 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php',
+ 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php',
+ 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php',
+ 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php',
+ 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php',
+ 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php',
+ 'yii\web\JsonParser' => YII_PATH . '/web/JsonParser.php',
+ 'yii\web\MethodNotAllowedHttpException' => YII_PATH . '/web/MethodNotAllowedHttpException.php',
+ 'yii\web\NotAcceptableHttpException' => YII_PATH . '/web/NotAcceptableHttpException.php',
+ 'yii\web\NotFoundHttpException' => YII_PATH . '/web/NotFoundHttpException.php',
+ 'yii\web\PageCache' => YII_PATH . '/web/PageCache.php',
+ 'yii\web\Request' => YII_PATH . '/web/Request.php',
+ 'yii\web\RequestParserInterface' => YII_PATH . '/web/RequestParserInterface.php',
+ 'yii\web\Response' => YII_PATH . '/web/Response.php',
+ 'yii\web\ResponseFormatterInterface' => YII_PATH . '/web/ResponseFormatterInterface.php',
+ 'yii\web\Session' => YII_PATH . '/web/Session.php',
+ 'yii\web\SessionIterator' => YII_PATH . '/web/SessionIterator.php',
+ 'yii\web\TooManyRequestsHttpException' => YII_PATH . '/web/TooManyRequestsHttpException.php',
+ 'yii\web\UnauthorizedHttpException' => YII_PATH . '/web/UnauthorizedHttpException.php',
+ 'yii\web\UnsupportedMediaTypeHttpException' => YII_PATH . '/web/UnsupportedMediaTypeHttpException.php',
+ 'yii\web\UploadedFile' => YII_PATH . '/web/UploadedFile.php',
+ 'yii\web\UrlManager' => YII_PATH . '/web/UrlManager.php',
+ 'yii\web\UrlRule' => YII_PATH . '/web/UrlRule.php',
+ 'yii\web\User' => YII_PATH . '/web/User.php',
+ 'yii\web\UserEvent' => YII_PATH . '/web/UserEvent.php',
+ 'yii\web\VerbFilter' => YII_PATH . '/web/VerbFilter.php',
+ 'yii\web\View' => YII_PATH . '/web/View.php',
+ 'yii\web\XmlResponseFormatter' => YII_PATH . '/web/XmlResponseFormatter.php',
+ 'yii\web\YiiAsset' => YII_PATH . '/web/YiiAsset.php',
+ 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php',
+ 'yii\widgets\ActiveForm' => YII_PATH . '/widgets/ActiveForm.php',
+ 'yii\widgets\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php',
+ 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php',
+ 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php',
+ 'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.php',
+ 'yii\widgets\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php',
+ 'yii\widgets\DetailView' => YII_PATH . '/widgets/DetailView.php',
+ 'yii\widgets\FragmentCache' => YII_PATH . '/widgets/FragmentCache.php',
+ 'yii\widgets\InputWidget' => YII_PATH . '/widgets/InputWidget.php',
+ 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php',
+ 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php',
+ 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php',
+ 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php',
+ 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php',
+ 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php',
+ 'yii\widgets\Pjax' => YII_PATH . '/widgets/Pjax.php',
+ 'yii\widgets\PjaxAsset' => YII_PATH . '/widgets/PjaxAsset.php',
+ 'yii\widgets\Spaceless' => YII_PATH . '/widgets/Spaceless.php',
];
diff --git a/framework/console/Application.php b/framework/console/Application.php
index 480311903cf..0a6b79363b8 100644
--- a/framework/console/Application.php
+++ b/framework/console/Application.php
@@ -53,154 +53,155 @@
*/
class Application extends \yii\base\Application
{
- /**
- * The option name for specifying the application configuration file path.
- */
- const OPTION_APPCONFIG = 'appconfig';
+ /**
+ * The option name for specifying the application configuration file path.
+ */
+ const OPTION_APPCONFIG = 'appconfig';
- /**
- * @var string the default route of this application. Defaults to 'help',
- * meaning the `help` command.
- */
- public $defaultRoute = 'help';
- /**
- * @var boolean whether to enable the commands provided by the core framework.
- * Defaults to true.
- */
- public $enableCoreCommands = true;
- /**
- * @var Controller the currently active controller instance
- */
- public $controller;
+ /**
+ * @var string the default route of this application. Defaults to 'help',
+ * meaning the `help` command.
+ */
+ public $defaultRoute = 'help';
+ /**
+ * @var boolean whether to enable the commands provided by the core framework.
+ * Defaults to true.
+ */
+ public $enableCoreCommands = true;
+ /**
+ * @var Controller the currently active controller instance
+ */
+ public $controller;
+ /**
+ * @inheritdoc
+ */
+ public function __construct($config = [])
+ {
+ $config = $this->loadConfig($config);
+ parent::__construct($config);
+ }
- /**
- * @inheritdoc
- */
- public function __construct($config = [])
- {
- $config = $this->loadConfig($config);
- parent::__construct($config);
- }
+ /**
+ * Loads the configuration.
+ * This method will check if the command line option [[OPTION_APPCONFIG]] is specified.
+ * If so, the corresponding file will be loaded as the application configuration.
+ * Otherwise, the configuration provided as the parameter will be returned back.
+ * @param array $config the configuration provided in the constructor.
+ * @return array the actual configuration to be used by the application.
+ */
+ protected function loadConfig($config)
+ {
+ if (!empty($_SERVER['argv'])) {
+ $option = '--' . self::OPTION_APPCONFIG . '=';
+ foreach ($_SERVER['argv'] as $param) {
+ if (strpos($param, $option) !== false) {
+ $path = substr($param, strlen($option));
+ if (!empty($path) && is_file($file = Yii::getAlias($path))) {
+ return require($file);
+ } else {
+ die("The configuration file does not exist: $path\n");
+ }
+ }
+ }
+ }
- /**
- * Loads the configuration.
- * This method will check if the command line option [[OPTION_APPCONFIG]] is specified.
- * If so, the corresponding file will be loaded as the application configuration.
- * Otherwise, the configuration provided as the parameter will be returned back.
- * @param array $config the configuration provided in the constructor.
- * @return array the actual configuration to be used by the application.
- */
- protected function loadConfig($config)
- {
- if (!empty($_SERVER['argv'])) {
- $option = '--' . self::OPTION_APPCONFIG . '=';
- foreach ($_SERVER['argv'] as $param) {
- if (strpos($param, $option) !== false) {
- $path = substr($param, strlen($option));
- if (!empty($path) && is_file($file = Yii::getAlias($path))) {
- return require($file);
- } else {
- die("The configuration file does not exist: $path\n");
- }
- }
- }
- }
- return $config;
- }
+ return $config;
+ }
- /**
- * Initialize the application.
- */
- public function init()
- {
- parent::init();
- if ($this->enableCoreCommands) {
- foreach ($this->coreCommands() as $id => $command) {
- if (!isset($this->controllerMap[$id])) {
- $this->controllerMap[$id] = $command;
- }
- }
- }
- // ensure we have the 'help' command so that we can list the available commands
- if (!isset($this->controllerMap['help'])) {
- $this->controllerMap['help'] = 'yii\console\controllers\HelpController';
- }
- }
+ /**
+ * Initialize the application.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->enableCoreCommands) {
+ foreach ($this->coreCommands() as $id => $command) {
+ if (!isset($this->controllerMap[$id])) {
+ $this->controllerMap[$id] = $command;
+ }
+ }
+ }
+ // ensure we have the 'help' command so that we can list the available commands
+ if (!isset($this->controllerMap['help'])) {
+ $this->controllerMap['help'] = 'yii\console\controllers\HelpController';
+ }
+ }
- /**
- * Handles the specified request.
- * @param Request $request the request to be handled
- * @return Response the resulting response
- */
- public function handleRequest($request)
- {
- list ($route, $params) = $request->resolve();
- $this->requestedRoute = $route;
- $result = $this->runAction($route, $params);
- if ($result instanceof Response) {
- return $result;
- } else {
- $response = $this->getResponse();
- $response->exitStatus = (int)$result;
- return $response;
- }
- }
+ /**
+ * Handles the specified request.
+ * @param Request $request the request to be handled
+ * @return Response the resulting response
+ */
+ public function handleRequest($request)
+ {
+ list ($route, $params) = $request->resolve();
+ $this->requestedRoute = $route;
+ $result = $this->runAction($route, $params);
+ if ($result instanceof Response) {
+ return $result;
+ } else {
+ $response = $this->getResponse();
+ $response->exitStatus = (int) $result;
- /**
- * Returns the response component.
- * @return Response the response component
- */
- public function getResponse()
- {
- return $this->getComponent('response');
- }
+ return $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.
- * If the route is empty, the method will use [[defaultRoute]].
- * @param string $route the route that specifies the action.
- * @param array $params the parameters to be passed to the action
- * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
- * @throws Exception if the route is invalid
- */
- public function runAction($route, $params = [])
- {
- try {
- return parent::runAction($route, $params);
- } catch (InvalidRouteException $e) {
- throw new Exception(Yii::t('yii', 'Unknown command "{command}".', ['command' => $route]), 0, $e);
- }
- }
+ /**
+ * Returns the response component.
+ * @return Response the response component
+ */
+ public function getResponse()
+ {
+ return $this->getComponent('response');
+ }
- /**
- * Returns the configuration of the built-in commands.
- * @return array the configuration of the built-in commands.
- */
- public function coreCommands()
- {
- return [
- 'message' => 'yii\console\controllers\MessageController',
- 'help' => 'yii\console\controllers\HelpController',
- 'migrate' => 'yii\console\controllers\MigrateController',
- 'cache' => 'yii\console\controllers\CacheController',
- 'asset' => 'yii\console\controllers\AssetController',
- 'fixture' => 'yii\console\controllers\FixtureController',
- ];
- }
+ /**
+ * 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.
+ * If the route is empty, the method will use [[defaultRoute]].
+ * @param string $route the route that specifies the action.
+ * @param array $params the parameters to be passed to the action
+ * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
+ * @throws Exception if the route is invalid
+ */
+ public function runAction($route, $params = [])
+ {
+ try {
+ return parent::runAction($route, $params);
+ } catch (InvalidRouteException $e) {
+ throw new Exception(Yii::t('yii', 'Unknown command "{command}".', ['command' => $route]), 0, $e);
+ }
+ }
- /**
- * Registers the core application components.
- * @see setComponents
- */
- public function registerCoreComponents()
- {
- parent::registerCoreComponents();
- $this->setComponents([
- 'request' => ['class' => 'yii\console\Request'],
- 'response' => ['class' => 'yii\console\Response'],
- ]);
- }
+ /**
+ * Returns the configuration of the built-in commands.
+ * @return array the configuration of the built-in commands.
+ */
+ public function coreCommands()
+ {
+ return [
+ 'message' => 'yii\console\controllers\MessageController',
+ 'help' => 'yii\console\controllers\HelpController',
+ 'migrate' => 'yii\console\controllers\MigrateController',
+ 'cache' => 'yii\console\controllers\CacheController',
+ 'asset' => 'yii\console\controllers\AssetController',
+ 'fixture' => 'yii\console\controllers\FixtureController',
+ ];
+ }
+
+ /**
+ * Registers the core application components.
+ * @see setComponents
+ */
+ public function registerCoreComponents()
+ {
+ parent::registerCoreComponents();
+ $this->setComponents([
+ 'request' => ['class' => 'yii\console\Request'],
+ 'response' => ['class' => 'yii\console\Response'],
+ ]);
+ }
}
diff --git a/framework/console/Controller.php b/framework/console/Controller.php
index 779b6615213..7f817bf5172 100644
--- a/framework/console/Controller.php
+++ b/framework/console/Controller.php
@@ -29,244 +29,247 @@
*/
class Controller extends \yii\base\Controller
{
- /**
- * @var boolean whether to run the command interactively.
- */
- public $interactive = true;
+ /**
+ * @var boolean whether to run the command interactively.
+ */
+ public $interactive = true;
- /**
- * @var boolean whether to enable ANSI color in the output.
- * If not set, ANSI color will only be enabled for terminals that support it.
- */
- public $color;
+ /**
+ * @var boolean whether to enable ANSI color in the output.
+ * If not set, ANSI color will only be enabled for terminals that support it.
+ */
+ public $color;
- /**
- * Returns a value indicating whether ANSI color is enabled.
- *
- * ANSI color is enabled only if [[color]] is set true or is not set
- * and the terminal supports ANSI color.
- *
- * @param resource $stream the stream to check.
- * @return boolean Whether to enable ANSI style in output.
- */
- public function isColorEnabled($stream = STDOUT)
- {
- return $this->color === null ? Console::streamSupportsAnsiColors($stream) : $this->color;
- }
+ /**
+ * Returns a value indicating whether ANSI color is enabled.
+ *
+ * ANSI color is enabled only if [[color]] is set true or is not set
+ * and the terminal supports ANSI color.
+ *
+ * @param resource $stream the stream to check.
+ * @return boolean Whether to enable ANSI style in output.
+ */
+ public function isColorEnabled($stream = STDOUT)
+ {
+ return $this->color === null ? Console::streamSupportsAnsiColors($stream) : $this->color;
+ }
- /**
- * Runs an action with the specified action ID and parameters.
- * If the action ID is empty, the method will use [[defaultAction]].
- * @param string $id the ID of the action to be executed.
- * @param array $params the parameters (name-value pairs) to be passed to the action.
- * @return integer the status of the action execution. 0 means normal, other values mean abnormal.
- * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
- * @throws Exception if there are unknown options or missing arguments
- * @see createAction
- */
- public function runAction($id, $params = [])
- {
- if (!empty($params)) {
- // populate options here so that they are available in beforeAction().
- $options = $this->options($id);
- foreach ($params as $name => $value) {
- if (in_array($name, $options, true)) {
- $default = $this->$name;
- $this->$name = is_array($default) ? preg_split('/\s*,\s*/', $value) : $value;
- unset($params[$name]);
- } elseif (!is_int($name)) {
- throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name]));
- }
- }
- }
- return parent::runAction($id, $params);
- }
+ /**
+ * Runs an action with the specified action ID and parameters.
+ * If the action ID is empty, the method will use [[defaultAction]].
+ * @param string $id the ID of the action to be executed.
+ * @param array $params the parameters (name-value pairs) to be passed to the action.
+ * @return integer the status of the action execution. 0 means normal, other values mean abnormal.
+ * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
+ * @throws Exception if there are unknown options or missing arguments
+ * @see createAction
+ */
+ public function runAction($id, $params = [])
+ {
+ if (!empty($params)) {
+ // populate options here so that they are available in beforeAction().
+ $options = $this->options($id);
+ foreach ($params as $name => $value) {
+ if (in_array($name, $options, true)) {
+ $default = $this->$name;
+ $this->$name = is_array($default) ? preg_split('/\s*,\s*/', $value) : $value;
+ unset($params[$name]);
+ } elseif (!is_int($name)) {
+ throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name]));
+ }
+ }
+ }
- /**
- * Binds the parameters to the action.
- * This method is invoked by [[Action]] when it begins to run with the given parameters.
- * This method will first bind the parameters with the [[options()|options]]
- * available to the action. It then validates the given arguments.
- * @param Action $action the action to be bound with parameters
- * @param array $params the parameters to be bound to the action
- * @return array the valid parameters that the action can run with.
- * @throws Exception if there are unknown options or missing arguments
- */
- public function bindActionParams($action, $params)
- {
- if ($action instanceof InlineAction) {
- $method = new \ReflectionMethod($this, $action->actionMethod);
- } else {
- $method = new \ReflectionMethod($action, 'run');
- }
+ return parent::runAction($id, $params);
+ }
- $args = array_values($params);
+ /**
+ * Binds the parameters to the action.
+ * This method is invoked by [[Action]] when it begins to run with the given parameters.
+ * This method will first bind the parameters with the [[options()|options]]
+ * available to the action. It then validates the given arguments.
+ * @param Action $action the action to be bound with parameters
+ * @param array $params the parameters to be bound to the action
+ * @return array the valid parameters that the action can run with.
+ * @throws Exception if there are unknown options or missing arguments
+ */
+ public function bindActionParams($action, $params)
+ {
+ if ($action instanceof InlineAction) {
+ $method = new \ReflectionMethod($this, $action->actionMethod);
+ } else {
+ $method = new \ReflectionMethod($action, 'run');
+ }
- $missing = [];
- foreach ($method->getParameters() as $i => $param) {
- if ($param->isArray() && isset($args[$i])) {
- $args[$i] = preg_split('/\s*,\s*/', $args[$i]);
- }
- if (!isset($args[$i])) {
- if ($param->isDefaultValueAvailable()) {
- $args[$i] = $param->getDefaultValue();
- } else {
- $missing[] = $param->getName();
- }
- }
- }
+ $args = array_values($params);
- if (!empty($missing)) {
- throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', $missing)]));
- }
+ $missing = [];
+ foreach ($method->getParameters() as $i => $param) {
+ if ($param->isArray() && isset($args[$i])) {
+ $args[$i] = preg_split('/\s*,\s*/', $args[$i]);
+ }
+ if (!isset($args[$i])) {
+ if ($param->isDefaultValueAvailable()) {
+ $args[$i] = $param->getDefaultValue();
+ } else {
+ $missing[] = $param->getName();
+ }
+ }
+ }
- return $args;
- }
+ if (!empty($missing)) {
+ throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', $missing)]));
+ }
- /**
- * Formats a string with ANSI codes
- *
- * You may pass additional parameters using the constants defined in [[\yii\helpers\Console]].
- *
- * Example:
- *
- * ~~~
- * echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
- * ~~~
- *
- * @param string $string the string to be formatted
- * @return string
- */
- public function ansiFormat($string)
- {
- if ($this->isColorEnabled()) {
- $args = func_get_args();
- array_shift($args);
- $string = Console::ansiFormat($string, $args);
- }
- return $string;
- }
+ return $args;
+ }
- /**
- * Prints a string to STDOUT
- *
- * You may optionally format the string with ANSI codes by
- * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
- *
- * Example:
- *
- * ~~~
- * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
- * ~~~
- *
- * @param string $string the string to print
- * @return int|boolean Number of bytes printed or false on error
- */
- public function stdout($string)
- {
- if ($this->isColorEnabled()) {
- $args = func_get_args();
- array_shift($args);
- $string = Console::ansiFormat($string, $args);
- }
- return Console::stdout($string);
- }
+ /**
+ * Formats a string with ANSI codes
+ *
+ * You may pass additional parameters using the constants defined in [[\yii\helpers\Console]].
+ *
+ * Example:
+ *
+ * ~~~
+ * echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
+ * ~~~
+ *
+ * @param string $string the string to be formatted
+ * @return string
+ */
+ public function ansiFormat($string)
+ {
+ if ($this->isColorEnabled()) {
+ $args = func_get_args();
+ array_shift($args);
+ $string = Console::ansiFormat($string, $args);
+ }
- /**
- * Prints a string to STDERR
- *
- * You may optionally format the string with ANSI codes by
- * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
- *
- * Example:
- *
- * ~~~
- * $this->stderr('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
- * ~~~
- *
- * @param string $string the string to print
- * @return int|boolean Number of bytes printed or false on error
- */
- public function stderr($string)
- {
- if ($this->isColorEnabled(STDERR)) {
- $args = func_get_args();
- array_shift($args);
- $string = Console::ansiFormat($string, $args);
- }
- return fwrite(STDERR, $string);
- }
+ return $string;
+ }
- /**
- * Prompts the user for input and validates it
- *
- * @param string $text prompt string
- * @param array $options the options to validate the input:
- *
- * - required: whether it is required or not
- * - default: default value if no input is inserted by the user
- * - pattern: regular expression pattern to validate user input
- * - validator: a callable function to validate input. The function must accept two parameters:
- * - $input: the user input to validate
- * - $error: the error value passed by reference if validation failed.
- * @return string the user input
- */
- public function prompt($text, $options = [])
- {
- if ($this->interactive) {
- return Console::prompt($text, $options);
- } else {
- return isset($options['default']) ? $options['default'] : '';
- }
- }
+ /**
+ * Prints a string to STDOUT
+ *
+ * You may optionally format the string with ANSI codes by
+ * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
+ *
+ * Example:
+ *
+ * ~~~
+ * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
+ * ~~~
+ *
+ * @param string $string the string to print
+ * @return int|boolean Number of bytes printed or false on error
+ */
+ public function stdout($string)
+ {
+ if ($this->isColorEnabled()) {
+ $args = func_get_args();
+ array_shift($args);
+ $string = Console::ansiFormat($string, $args);
+ }
- /**
- * Asks user to confirm by typing y or n.
- *
- * @param string $message to echo out before waiting for user input
- * @param boolean $default this value is returned if no selection is made.
- * @return boolean whether user confirmed.
- * Will return true if [[interactive]] is false.
- */
- public function confirm($message, $default = false)
- {
- if ($this->interactive) {
- return Console::confirm($message, $default);
- } else {
- return true;
- }
- }
+ return Console::stdout($string);
+ }
- /**
- * Gives the user an option to choose from. Giving '?' as an input will show
- * a list of options to choose from and their explanations.
- *
- * @param string $prompt the prompt message
- * @param array $options Key-value array of options to choose from
- *
- * @return string An option character the user chose
- */
- public function select($prompt, $options = [])
- {
- return Console::select($prompt, $options);
- }
+ /**
+ * Prints a string to STDERR
+ *
+ * You may optionally format the string with ANSI codes by
+ * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
+ *
+ * Example:
+ *
+ * ~~~
+ * $this->stderr('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
+ * ~~~
+ *
+ * @param string $string the string to print
+ * @return int|boolean Number of bytes printed or false on error
+ */
+ public function stderr($string)
+ {
+ if ($this->isColorEnabled(STDERR)) {
+ $args = func_get_args();
+ array_shift($args);
+ $string = Console::ansiFormat($string, $args);
+ }
+ return fwrite(STDERR, $string);
+ }
- /**
- * Returns the names of valid options for the action (id)
- * An option requires the existence of a public member variable whose
- * name is the option name.
- * Child classes may override this method to specify possible options.
- *
- * Note that the values setting via options are not available
- * until [[beforeAction()]] is being called.
- *
- * @param $id action name
- * @return array the names of the options valid for the action
- */
- public function options($id)
- {
- // $id might be used in subclass to provide options specific to action id
- return ['color', 'interactive'];
- }
+ /**
+ * Prompts the user for input and validates it
+ *
+ * @param string $text prompt string
+ * @param array $options the options to validate the input:
+ *
+ * - required: whether it is required or not
+ * - default: default value if no input is inserted by the user
+ * - pattern: regular expression pattern to validate user input
+ * - validator: a callable function to validate input. The function must accept two parameters:
+ * - $input: the user input to validate
+ * - $error: the error value passed by reference if validation failed.
+ * @return string the user input
+ */
+ public function prompt($text, $options = [])
+ {
+ if ($this->interactive) {
+ return Console::prompt($text, $options);
+ } else {
+ return isset($options['default']) ? $options['default'] : '';
+ }
+ }
+
+ /**
+ * Asks user to confirm by typing y or n.
+ *
+ * @param string $message to echo out before waiting for user input
+ * @param boolean $default this value is returned if no selection is made.
+ * @return boolean whether user confirmed.
+ * Will return true if [[interactive]] is false.
+ */
+ public function confirm($message, $default = false)
+ {
+ if ($this->interactive) {
+ return Console::confirm($message, $default);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Gives the user an option to choose from. Giving '?' as an input will show
+ * a list of options to choose from and their explanations.
+ *
+ * @param string $prompt the prompt message
+ * @param array $options Key-value array of options to choose from
+ *
+ * @return string An option character the user chose
+ */
+ public function select($prompt, $options = [])
+ {
+ return Console::select($prompt, $options);
+ }
+
+ /**
+ * Returns the names of valid options for the action (id)
+ * An option requires the existence of a public member variable whose
+ * name is the option name.
+ * Child classes may override this method to specify possible options.
+ *
+ * Note that the values setting via options are not available
+ * until [[beforeAction()]] is being called.
+ *
+ * @param $id action name
+ * @return array the names of the options valid for the action
+ */
+ public function options($id)
+ {
+ // $id might be used in subclass to provide options specific to action id
+ return ['color', 'interactive'];
+ }
}
diff --git a/framework/console/Exception.php b/framework/console/Exception.php
index 5148361bffa..41a01e475a0 100644
--- a/framework/console/Exception.php
+++ b/framework/console/Exception.php
@@ -17,11 +17,11 @@
*/
class Exception extends UserException
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Error';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Error';
+ }
}
diff --git a/framework/console/Request.php b/framework/console/Request.php
index e5aa9fdac3f..fa017e4816d 100644
--- a/framework/console/Request.php
+++ b/framework/console/Request.php
@@ -20,60 +20,61 @@
*/
class Request extends \yii\base\Request
{
- private $_params;
+ private $_params;
- /**
- * Returns the command line arguments.
- * @return array the command line arguments. It does not include the entry script name.
- */
- public function getParams()
- {
- if (!isset($this->_params)) {
- if (isset($_SERVER['argv'])) {
- $this->_params = $_SERVER['argv'];
- array_shift($this->_params);
- } else {
- $this->_params = [];
- }
- }
- return $this->_params;
- }
+ /**
+ * Returns the command line arguments.
+ * @return array the command line arguments. It does not include the entry script name.
+ */
+ public function getParams()
+ {
+ if (!isset($this->_params)) {
+ if (isset($_SERVER['argv'])) {
+ $this->_params = $_SERVER['argv'];
+ array_shift($this->_params);
+ } else {
+ $this->_params = [];
+ }
+ }
- /**
- * Sets the command line arguments.
- * @param array $params the command line arguments
- */
- public function setParams($params)
- {
- $this->_params = $params;
- }
+ return $this->_params;
+ }
- /**
- * Resolves the current request into a route and the associated parameters.
- * @return array the first element is the route, and the second is the associated parameters.
- */
- public function resolve()
- {
- $rawParams = $this->getParams();
- if (isset($rawParams[0])) {
- $route = $rawParams[0];
- array_shift($rawParams);
- } else {
- $route = '';
- }
+ /**
+ * Sets the command line arguments.
+ * @param array $params the command line arguments
+ */
+ public function setParams($params)
+ {
+ $this->_params = $params;
+ }
- $params = [];
- foreach ($rawParams as $param) {
- if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
- $name = $matches[1];
- if ($name !== Application::OPTION_APPCONFIG) {
- $params[$name] = isset($matches[3]) ? $matches[3] : true;
- }
- } else {
- $params[] = $param;
- }
- }
+ /**
+ * Resolves the current request into a route and the associated parameters.
+ * @return array the first element is the route, and the second is the associated parameters.
+ */
+ public function resolve()
+ {
+ $rawParams = $this->getParams();
+ if (isset($rawParams[0])) {
+ $route = $rawParams[0];
+ array_shift($rawParams);
+ } else {
+ $route = '';
+ }
- return [$route, $params];
- }
+ $params = [];
+ foreach ($rawParams as $param) {
+ if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
+ $name = $matches[1];
+ if ($name !== Application::OPTION_APPCONFIG) {
+ $params[$name] = isset($matches[3]) ? $matches[3] : true;
+ }
+ } else {
+ $params[] = $param;
+ }
+ }
+
+ return [$route, $params];
+ }
}
diff --git a/framework/console/controllers/AssetController.php b/framework/console/controllers/AssetController.php
index 5f5eeaa2eb3..6f71365d1d0 100644
--- a/framework/console/controllers/AssetController.php
+++ b/framework/console/controllers/AssetController.php
@@ -36,363 +36,365 @@
*/
class AssetController extends Controller
{
- /**
- * @var string controller default action ID.
- */
- public $defaultAction = 'compress';
- /**
- * @var array list of asset bundles to be compressed.
- */
- public $bundles = [];
- /**
- * @var array list of asset bundles, which represents output compressed files.
- * You can specify the name of the output compressed file using 'css' and 'js' keys:
- * For example:
- *
- * ~~~
- * 'app\config\AllAsset' => [
- * 'js' => 'js/all-{ts}.js',
- * 'css' => 'css/all-{ts}.css',
- * 'depends' => [ ... ],
- * ]
- * ~~~
- *
- * File names can contain placeholder "{ts}", which will be filled by current timestamp, while
- * file creation.
- */
- public $targets = [];
- /**
- * @var string|callable JavaScript file compressor.
- * If a string, it is treated as shell command template, which should contain
- * placeholders {from} - source file name - and {to} - output file name.
- * Otherwise, it is treated as PHP callback, which should perform the compression.
- *
- * Default value relies on usage of "Closure Compiler"
- * @see https://developers.google.com/closure/compiler/
- */
- public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}';
- /**
- * @var string|callable CSS file compressor.
- * If a string, it is treated as shell command template, which should contain
- * placeholders {from} - source file name - and {to} - output file name.
- * Otherwise, it is treated as PHP callback, which should perform the compression.
- *
- * Default value relies on usage of "YUI Compressor"
- * @see https://github.com/yui/yuicompressor/
- */
- public $cssCompressor = 'java -jar yuicompressor.jar --type css {from} -o {to}';
-
- /**
- * @var array|\yii\web\AssetManager [[\yii\web\AssetManager]] instance or its array configuration, which will be used
- * for assets processing.
- */
- private $_assetManager = [];
-
-
- /**
- * Returns the asset manager instance.
- * @throws \yii\console\Exception on invalid configuration.
- * @return \yii\web\AssetManager asset manager instance.
- */
- public function getAssetManager()
- {
- if (!is_object($this->_assetManager)) {
- $options = $this->_assetManager;
- if (!isset($options['class'])) {
- $options['class'] = 'yii\\web\\AssetManager';
- }
- if (!isset($options['basePath'])) {
- throw new Exception("Please specify 'basePath' for the 'assetManager' option.");
- }
- if (!isset($options['baseUrl'])) {
- throw new Exception("Please specify 'baseUrl' for the 'assetManager' option.");
- }
- $this->_assetManager = Yii::createObject($options);
- }
- return $this->_assetManager;
- }
-
- /**
- * Sets asset manager instance or configuration.
- * @param \yii\web\AssetManager|array $assetManager asset manager instance or its array configuration.
- * @throws \yii\console\Exception on invalid argument type.
- */
- public function setAssetManager($assetManager)
- {
- if (is_scalar($assetManager)) {
- throw new Exception('"' . get_class($this) . '::assetManager" should be either object or array - "' . gettype($assetManager) . '" given.');
- }
- $this->_assetManager = $assetManager;
- }
-
- /**
- * Combines and compresses the asset files according to the given configuration.
- * During the process new asset bundle configuration file will be created.
- * You should replace your original asset bundle configuration with this file in order to use compressed files.
- * @param string $configFile configuration file name.
- * @param string $bundleFile output asset bundles configuration file name.
- */
- public function actionCompress($configFile, $bundleFile)
- {
- $this->loadConfiguration($configFile);
- $bundles = $this->loadBundles($this->bundles);
- $targets = $this->loadTargets($this->targets, $bundles);
- $timestamp = time();
- foreach ($targets as $name => $target) {
- echo "Creating output bundle '{$name}':\n";
- if (!empty($target->js)) {
- $this->buildTarget($target, 'js', $bundles, $timestamp);
- }
- if (!empty($target->css)) {
- $this->buildTarget($target, 'css', $bundles, $timestamp);
- }
- echo "\n";
- }
-
- $targets = $this->adjustDependency($targets, $bundles);
- $this->saveTargets($targets, $bundleFile);
- }
-
- /**
- * Applies configuration from the given file to self instance.
- * @param string $configFile configuration file name.
- * @throws \yii\console\Exception on failure.
- */
- protected function loadConfiguration($configFile)
- {
- echo "Loading configuration from '{$configFile}'...\n";
- foreach (require($configFile) as $name => $value) {
- if (property_exists($this, $name) || $this->canSetProperty($name)) {
- $this->$name = $value;
- } else {
- throw new Exception("Unknown configuration option: $name");
- }
- }
-
- $this->getAssetManager(); // check if asset manager configuration is correct
- }
-
- /**
- * Creates full list of source asset bundles.
- * @param string[] $bundles list of asset bundle names
- * @return \yii\web\AssetBundle[] list of source asset bundles.
- */
- protected function loadBundles($bundles)
- {
- echo "Collecting source bundles information...\n";
-
- $am = $this->getAssetManager();
- $result = [];
- foreach ($bundles as $name) {
- $result[$name] = $am->getBundle($name);
- }
- foreach ($result as $bundle) {
- $this->loadDependency($bundle, $result);
- }
-
- return $result;
- }
-
- /**
- * Loads asset bundle dependencies recursively.
- * @param \yii\web\AssetBundle $bundle bundle instance
- * @param array $result already loaded bundles list.
- * @throws Exception on failure.
- */
- protected function loadDependency($bundle, &$result)
- {
- $am = $this->getAssetManager();
- foreach ($bundle->depends as $name) {
- if (!isset($result[$name])) {
- $dependencyBundle = $am->getBundle($name);
- $result[$name] = false;
- $this->loadDependency($dependencyBundle, $result);
- $result[$name] = $dependencyBundle;
- } elseif ($result[$name] === false) {
- throw new Exception("A circular dependency is detected for bundle '$name'.");
- }
- }
- }
-
- /**
- * Creates full list of output asset bundles.
- * @param array $targets output asset bundles configuration.
- * @param \yii\web\AssetBundle[] $bundles list of source asset bundles.
- * @return \yii\web\AssetBundle[] list of output asset bundles.
- * @throws Exception on failure.
- */
- protected function loadTargets($targets, $bundles)
- {
- // build the dependency order of bundles
- $registered = [];
- foreach ($bundles as $name => $bundle) {
- $this->registerBundle($bundles, $name, $registered);
- }
- $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1));
-
- // fill up the target which has empty 'depends'.
- $referenced = [];
- foreach ($targets as $name => $target) {
- if (empty($target['depends'])) {
- if (!isset($all)) {
- $all = $name;
- } else {
- throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name");
- }
- } else {
- foreach ($target['depends'] as $bundle) {
- if (!isset($referenced[$bundle])) {
- $referenced[$bundle] = $name;
- } else {
- throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time.");
- }
- }
- }
- }
- if (isset($all)) {
- $targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced));
- }
-
- // adjust the 'depends' order for each target according to the dependency order of bundles
- // create an AssetBundle object for each target
- foreach ($targets as $name => $target) {
- if (!isset($target['basePath'])) {
- throw new Exception("Please specify 'basePath' for the '$name' target.");
- }
- if (!isset($target['baseUrl'])) {
- throw new Exception("Please specify 'baseUrl' for the '$name' target.");
- }
- usort($target['depends'], function ($a, $b) use ($bundleOrders) {
- if ($bundleOrders[$a] == $bundleOrders[$b]) {
- return 0;
- } else {
- return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1;
- }
- });
- $target['class'] = $name;
- $targets[$name] = Yii::createObject($target);
- }
- return $targets;
- }
-
- /**
- * Builds output asset bundle.
- * @param \yii\web\AssetBundle $target output asset bundle
- * @param string $type either 'js' or 'css'.
- * @param \yii\web\AssetBundle[] $bundles source asset bundles.
- * @param integer $timestamp current timestamp.
- * @throws Exception on failure.
- */
- protected function buildTarget($target, $type, $bundles, $timestamp)
- {
- $outputFile = strtr($target->$type, [
- '{ts}' => $timestamp,
- ]);
- $inputFiles = [];
-
- foreach ($target->depends as $name) {
- if (isset($bundles[$name])) {
- foreach ($bundles[$name]->$type as $file) {
- $inputFiles[] = $bundles[$name]->basePath . '/' . $file;
- }
- } else {
- throw new Exception("Unknown bundle: '{$name}'");
- }
- }
- if ($type === 'js') {
- $this->compressJsFiles($inputFiles, $target->basePath . '/' . $outputFile);
- } else {
- $this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile);
- }
- $target->$type = [$outputFile];
- }
-
- /**
- * Adjust dependencies between asset bundles in the way source bundles begin to depend on output ones.
- * @param \yii\web\AssetBundle[] $targets output asset bundles.
- * @param \yii\web\AssetBundle[] $bundles source asset bundles.
- * @return \yii\web\AssetBundle[] output asset bundles.
- */
- protected function adjustDependency($targets, $bundles)
- {
- echo "Creating new bundle configuration...\n";
-
- $map = [];
- foreach ($targets as $name => $target) {
- foreach ($target->depends as $bundle) {
- $map[$bundle] = $name;
- }
- }
-
- foreach ($targets as $name => $target) {
- $depends = [];
- foreach ($target->depends as $bn) {
- foreach ($bundles[$bn]->depends as $bundle) {
- $depends[$map[$bundle]] = true;
- }
- }
- unset($depends[$name]);
- $target->depends = array_keys($depends);
- }
-
- // detect possible circular dependencies
- foreach ($targets as $name => $target) {
- $registered = [];
- $this->registerBundle($targets, $name, $registered);
- }
-
- foreach ($map as $bundle => $target) {
- $targets[$bundle] = Yii::createObject([
- 'class' => 'yii\\web\\AssetBundle',
- 'depends' => [$target],
- ]);
- }
- return $targets;
- }
-
- /**
- * Registers asset bundles including their dependencies.
- * @param \yii\web\AssetBundle[] $bundles asset bundles list.
- * @param string $name bundle name.
- * @param array $registered stores already registered names.
- * @throws Exception if circular dependency is detected.
- */
- protected function registerBundle($bundles, $name, &$registered)
- {
- if (!isset($registered[$name])) {
- $registered[$name] = false;
- $bundle = $bundles[$name];
- foreach ($bundle->depends as $depend) {
- $this->registerBundle($bundles, $depend, $registered);
- }
- unset($registered[$name]);
- $registered[$name] = true;
- } elseif ($registered[$name] === false) {
- throw new Exception("A circular dependency is detected for target '$name'.");
- }
- }
-
- /**
- * Saves new asset bundles configuration.
- * @param \yii\web\AssetBundle[] $targets list of asset bundles to be saved.
- * @param string $bundleFile output file name.
- * @throws \yii\console\Exception on failure.
- */
- protected function saveTargets($targets, $bundleFile)
- {
- $array = [];
- foreach ($targets as $name => $target) {
- foreach (['basePath', 'baseUrl', 'js', 'css', 'depends'] as $prop) {
- if (!empty($target->$prop)) {
- $array[$name][$prop] = $target->$prop;
- } elseif (in_array($prop, ['js', 'css'])) {
- $array[$name][$prop] = [];
- }
- }
- }
- $array = var_export($array, true);
- $version = date('Y-m-d H:i:s', time());
- $bundleFileContent = << [
+ * 'js' => 'js/all-{ts}.js',
+ * 'css' => 'css/all-{ts}.css',
+ * 'depends' => [ ... ],
+ * ]
+ * ~~~
+ *
+ * File names can contain placeholder "{ts}", which will be filled by current timestamp, while
+ * file creation.
+ */
+ public $targets = [];
+ /**
+ * @var string|callable JavaScript file compressor.
+ * If a string, it is treated as shell command template, which should contain
+ * placeholders {from} - source file name - and {to} - output file name.
+ * Otherwise, it is treated as PHP callback, which should perform the compression.
+ *
+ * Default value relies on usage of "Closure Compiler"
+ * @see https://developers.google.com/closure/compiler/
+ */
+ public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}';
+ /**
+ * @var string|callable CSS file compressor.
+ * If a string, it is treated as shell command template, which should contain
+ * placeholders {from} - source file name - and {to} - output file name.
+ * Otherwise, it is treated as PHP callback, which should perform the compression.
+ *
+ * Default value relies on usage of "YUI Compressor"
+ * @see https://github.com/yui/yuicompressor/
+ */
+ public $cssCompressor = 'java -jar yuicompressor.jar --type css {from} -o {to}';
+
+ /**
+ * @var array|\yii\web\AssetManager [[\yii\web\AssetManager]] instance or its array configuration, which will be used
+ * for assets processing.
+ */
+ private $_assetManager = [];
+
+ /**
+ * Returns the asset manager instance.
+ * @throws \yii\console\Exception on invalid configuration.
+ * @return \yii\web\AssetManager asset manager instance.
+ */
+ public function getAssetManager()
+ {
+ if (!is_object($this->_assetManager)) {
+ $options = $this->_assetManager;
+ if (!isset($options['class'])) {
+ $options['class'] = 'yii\\web\\AssetManager';
+ }
+ if (!isset($options['basePath'])) {
+ throw new Exception("Please specify 'basePath' for the 'assetManager' option.");
+ }
+ if (!isset($options['baseUrl'])) {
+ throw new Exception("Please specify 'baseUrl' for the 'assetManager' option.");
+ }
+ $this->_assetManager = Yii::createObject($options);
+ }
+
+ return $this->_assetManager;
+ }
+
+ /**
+ * Sets asset manager instance or configuration.
+ * @param \yii\web\AssetManager|array $assetManager asset manager instance or its array configuration.
+ * @throws \yii\console\Exception on invalid argument type.
+ */
+ public function setAssetManager($assetManager)
+ {
+ if (is_scalar($assetManager)) {
+ throw new Exception('"' . get_class($this) . '::assetManager" should be either object or array - "' . gettype($assetManager) . '" given.');
+ }
+ $this->_assetManager = $assetManager;
+ }
+
+ /**
+ * Combines and compresses the asset files according to the given configuration.
+ * During the process new asset bundle configuration file will be created.
+ * You should replace your original asset bundle configuration with this file in order to use compressed files.
+ * @param string $configFile configuration file name.
+ * @param string $bundleFile output asset bundles configuration file name.
+ */
+ public function actionCompress($configFile, $bundleFile)
+ {
+ $this->loadConfiguration($configFile);
+ $bundles = $this->loadBundles($this->bundles);
+ $targets = $this->loadTargets($this->targets, $bundles);
+ $timestamp = time();
+ foreach ($targets as $name => $target) {
+ echo "Creating output bundle '{$name}':\n";
+ if (!empty($target->js)) {
+ $this->buildTarget($target, 'js', $bundles, $timestamp);
+ }
+ if (!empty($target->css)) {
+ $this->buildTarget($target, 'css', $bundles, $timestamp);
+ }
+ echo "\n";
+ }
+
+ $targets = $this->adjustDependency($targets, $bundles);
+ $this->saveTargets($targets, $bundleFile);
+ }
+
+ /**
+ * Applies configuration from the given file to self instance.
+ * @param string $configFile configuration file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ protected function loadConfiguration($configFile)
+ {
+ echo "Loading configuration from '{$configFile}'...\n";
+ foreach (require($configFile) as $name => $value) {
+ if (property_exists($this, $name) || $this->canSetProperty($name)) {
+ $this->$name = $value;
+ } else {
+ throw new Exception("Unknown configuration option: $name");
+ }
+ }
+
+ $this->getAssetManager(); // check if asset manager configuration is correct
+ }
+
+ /**
+ * Creates full list of source asset bundles.
+ * @param string[] $bundles list of asset bundle names
+ * @return \yii\web\AssetBundle[] list of source asset bundles.
+ */
+ protected function loadBundles($bundles)
+ {
+ echo "Collecting source bundles information...\n";
+
+ $am = $this->getAssetManager();
+ $result = [];
+ foreach ($bundles as $name) {
+ $result[$name] = $am->getBundle($name);
+ }
+ foreach ($result as $bundle) {
+ $this->loadDependency($bundle, $result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Loads asset bundle dependencies recursively.
+ * @param \yii\web\AssetBundle $bundle bundle instance
+ * @param array $result already loaded bundles list.
+ * @throws Exception on failure.
+ */
+ protected function loadDependency($bundle, &$result)
+ {
+ $am = $this->getAssetManager();
+ foreach ($bundle->depends as $name) {
+ if (!isset($result[$name])) {
+ $dependencyBundle = $am->getBundle($name);
+ $result[$name] = false;
+ $this->loadDependency($dependencyBundle, $result);
+ $result[$name] = $dependencyBundle;
+ } elseif ($result[$name] === false) {
+ throw new Exception("A circular dependency is detected for bundle '$name'.");
+ }
+ }
+ }
+
+ /**
+ * Creates full list of output asset bundles.
+ * @param array $targets output asset bundles configuration.
+ * @param \yii\web\AssetBundle[] $bundles list of source asset bundles.
+ * @return \yii\web\AssetBundle[] list of output asset bundles.
+ * @throws Exception on failure.
+ */
+ protected function loadTargets($targets, $bundles)
+ {
+ // build the dependency order of bundles
+ $registered = [];
+ foreach ($bundles as $name => $bundle) {
+ $this->registerBundle($bundles, $name, $registered);
+ }
+ $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1));
+
+ // fill up the target which has empty 'depends'.
+ $referenced = [];
+ foreach ($targets as $name => $target) {
+ if (empty($target['depends'])) {
+ if (!isset($all)) {
+ $all = $name;
+ } else {
+ throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name");
+ }
+ } else {
+ foreach ($target['depends'] as $bundle) {
+ if (!isset($referenced[$bundle])) {
+ $referenced[$bundle] = $name;
+ } else {
+ throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time.");
+ }
+ }
+ }
+ }
+ if (isset($all)) {
+ $targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced));
+ }
+
+ // adjust the 'depends' order for each target according to the dependency order of bundles
+ // create an AssetBundle object for each target
+ foreach ($targets as $name => $target) {
+ if (!isset($target['basePath'])) {
+ throw new Exception("Please specify 'basePath' for the '$name' target.");
+ }
+ if (!isset($target['baseUrl'])) {
+ throw new Exception("Please specify 'baseUrl' for the '$name' target.");
+ }
+ usort($target['depends'], function ($a, $b) use ($bundleOrders) {
+ if ($bundleOrders[$a] == $bundleOrders[$b]) {
+ return 0;
+ } else {
+ return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1;
+ }
+ });
+ $target['class'] = $name;
+ $targets[$name] = Yii::createObject($target);
+ }
+
+ return $targets;
+ }
+
+ /**
+ * Builds output asset bundle.
+ * @param \yii\web\AssetBundle $target output asset bundle
+ * @param string $type either 'js' or 'css'.
+ * @param \yii\web\AssetBundle[] $bundles source asset bundles.
+ * @param integer $timestamp current timestamp.
+ * @throws Exception on failure.
+ */
+ protected function buildTarget($target, $type, $bundles, $timestamp)
+ {
+ $outputFile = strtr($target->$type, [
+ '{ts}' => $timestamp,
+ ]);
+ $inputFiles = [];
+
+ foreach ($target->depends as $name) {
+ if (isset($bundles[$name])) {
+ foreach ($bundles[$name]->$type as $file) {
+ $inputFiles[] = $bundles[$name]->basePath . '/' . $file;
+ }
+ } else {
+ throw new Exception("Unknown bundle: '{$name}'");
+ }
+ }
+ if ($type === 'js') {
+ $this->compressJsFiles($inputFiles, $target->basePath . '/' . $outputFile);
+ } else {
+ $this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile);
+ }
+ $target->$type = [$outputFile];
+ }
+
+ /**
+ * Adjust dependencies between asset bundles in the way source bundles begin to depend on output ones.
+ * @param \yii\web\AssetBundle[] $targets output asset bundles.
+ * @param \yii\web\AssetBundle[] $bundles source asset bundles.
+ * @return \yii\web\AssetBundle[] output asset bundles.
+ */
+ protected function adjustDependency($targets, $bundles)
+ {
+ echo "Creating new bundle configuration...\n";
+
+ $map = [];
+ foreach ($targets as $name => $target) {
+ foreach ($target->depends as $bundle) {
+ $map[$bundle] = $name;
+ }
+ }
+
+ foreach ($targets as $name => $target) {
+ $depends = [];
+ foreach ($target->depends as $bn) {
+ foreach ($bundles[$bn]->depends as $bundle) {
+ $depends[$map[$bundle]] = true;
+ }
+ }
+ unset($depends[$name]);
+ $target->depends = array_keys($depends);
+ }
+
+ // detect possible circular dependencies
+ foreach ($targets as $name => $target) {
+ $registered = [];
+ $this->registerBundle($targets, $name, $registered);
+ }
+
+ foreach ($map as $bundle => $target) {
+ $targets[$bundle] = Yii::createObject([
+ 'class' => 'yii\\web\\AssetBundle',
+ 'depends' => [$target],
+ ]);
+ }
+
+ return $targets;
+ }
+
+ /**
+ * Registers asset bundles including their dependencies.
+ * @param \yii\web\AssetBundle[] $bundles asset bundles list.
+ * @param string $name bundle name.
+ * @param array $registered stores already registered names.
+ * @throws Exception if circular dependency is detected.
+ */
+ protected function registerBundle($bundles, $name, &$registered)
+ {
+ if (!isset($registered[$name])) {
+ $registered[$name] = false;
+ $bundle = $bundles[$name];
+ foreach ($bundle->depends as $depend) {
+ $this->registerBundle($bundles, $depend, $registered);
+ }
+ unset($registered[$name]);
+ $registered[$name] = true;
+ } elseif ($registered[$name] === false) {
+ throw new Exception("A circular dependency is detected for target '$name'.");
+ }
+ }
+
+ /**
+ * Saves new asset bundles configuration.
+ * @param \yii\web\AssetBundle[] $targets list of asset bundles to be saved.
+ * @param string $bundleFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ protected function saveTargets($targets, $bundleFile)
+ {
+ $array = [];
+ foreach ($targets as $name => $target) {
+ foreach (['basePath', 'baseUrl', 'js', 'css', 'depends'] as $prop) {
+ if (!empty($target->$prop)) {
+ $array[$name][$prop] = $target->$prop;
+ } elseif (in_array($prop, ['js', 'css'])) {
+ $array[$name][$prop] = [];
+ }
+ }
+ }
+ $array = var_export($array, true);
+ $version = date('Y-m-d H:i:s', time());
+ $bundleFileContent = <<id}" command.
@@ -401,177 +403,177 @@ protected function saveTargets($targets, $bundleFile)
*/
return {$array};
EOD;
- if (!file_put_contents($bundleFile, $bundleFileContent)) {
- throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'.");
- }
- echo "Output bundle configuration created at '{$bundleFile}'.\n";
- }
-
- /**
- * Compresses given JavaScript files and combines them into the single one.
- * @param array $inputFiles list of source file names.
- * @param string $outputFile output file name.
- * @throws \yii\console\Exception on failure
- */
- protected function compressJsFiles($inputFiles, $outputFile)
- {
- if (empty($inputFiles)) {
- return;
- }
- echo " Compressing JavaScript files...\n";
- if (is_string($this->jsCompressor)) {
- $tmpFile = $outputFile . '.tmp';
- $this->combineJsFiles($inputFiles, $tmpFile);
- echo shell_exec(strtr($this->jsCompressor, [
- '{from}' => escapeshellarg($tmpFile),
- '{to}' => escapeshellarg($outputFile),
- ]));
- @unlink($tmpFile);
- } else {
- call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile);
- }
- if (!file_exists($outputFile)) {
- throw new Exception("Unable to compress JavaScript files into '{$outputFile}'.");
- }
- echo " JavaScript files compressed into '{$outputFile}'.\n";
- }
-
- /**
- * Compresses given CSS files and combines them into the single one.
- * @param array $inputFiles list of source file names.
- * @param string $outputFile output file name.
- * @throws \yii\console\Exception on failure
- */
- protected function compressCssFiles($inputFiles, $outputFile)
- {
- if (empty($inputFiles)) {
- return;
- }
- echo " Compressing CSS files...\n";
- if (is_string($this->cssCompressor)) {
- $tmpFile = $outputFile . '.tmp';
- $this->combineCssFiles($inputFiles, $tmpFile);
- echo shell_exec(strtr($this->cssCompressor, [
- '{from}' => escapeshellarg($tmpFile),
- '{to}' => escapeshellarg($outputFile),
- ]));
- @unlink($tmpFile);
- } else {
- call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile);
- }
- if (!file_exists($outputFile)) {
- throw new Exception("Unable to compress CSS files into '{$outputFile}'.");
- }
- echo " CSS files compressed into '{$outputFile}'.\n";
- }
-
- /**
- * Combines JavaScript files into a single one.
- * @param array $inputFiles source file names.
- * @param string $outputFile output file name.
- * @throws \yii\console\Exception on failure.
- */
- public function combineJsFiles($inputFiles, $outputFile)
- {
- $content = '';
- foreach ($inputFiles as $file) {
- $content .= "/*** BEGIN FILE: $file ***/\n"
- . file_get_contents($file)
- . "/*** END FILE: $file ***/\n";
- }
- if (!file_put_contents($outputFile, $content)) {
- throw new Exception("Unable to write output JavaScript file '{$outputFile}'.");
- }
- }
-
- /**
- * Combines CSS files into a single one.
- * @param array $inputFiles source file names.
- * @param string $outputFile output file name.
- * @throws \yii\console\Exception on failure.
- */
- public function combineCssFiles($inputFiles, $outputFile)
- {
- $content = '';
- foreach ($inputFiles as $file) {
- $content .= "/*** BEGIN FILE: $file ***/\n"
- . $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile))
- . "/*** END FILE: $file ***/\n";
- }
- if (!file_put_contents($outputFile, $content)) {
- throw new Exception("Unable to write output CSS file '{$outputFile}'.");
- }
- }
-
- /**
- * Adjusts CSS content allowing URL references pointing to the original resources.
- * @param string $cssContent source CSS content.
- * @param string $inputFilePath input CSS file name.
- * @param string $outputFilePath output CSS file name.
- * @return string adjusted CSS content.
- */
- protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath)
- {
- $sharedPathParts = [];
- $inputFilePathParts = explode('/', $inputFilePath);
- $inputFilePathPartsCount = count($inputFilePathParts);
- $outputFilePathParts = explode('/', $outputFilePath);
- $outputFilePathPartsCount = count($outputFilePathParts);
- for ($i =0; $i < $inputFilePathPartsCount && $i < $outputFilePathPartsCount; $i++) {
- if ($inputFilePathParts[$i] == $outputFilePathParts[$i]) {
- $sharedPathParts[] = $inputFilePathParts[$i];
- } else {
- break;
- }
- }
- $sharedPath = implode('/', $sharedPathParts);
-
- $inputFileRelativePath = trim(str_replace($sharedPath, '', $inputFilePath), '/');
- $outputFileRelativePath = trim(str_replace($sharedPath, '', $outputFilePath), '/');
- $inputFileRelativePathParts = explode('/', $inputFileRelativePath);
- $outputFileRelativePathParts = explode('/', $outputFileRelativePath);
-
- $callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
- $fullMatch = $matches[0];
- $inputUrl = $matches[1];
-
- if (preg_match('/https?:\/\//is', $inputUrl)) {
- return $fullMatch;
- }
-
- $outputUrlParts = array_fill(0, count($outputFileRelativePathParts), '..');
- $outputUrlParts = array_merge($outputUrlParts, $inputFileRelativePathParts);
-
- if (strpos($inputUrl, '/') !== false) {
- $inputUrlParts = explode('/', $inputUrl);
- foreach ($inputUrlParts as $key => $inputUrlPart) {
- if ($inputUrlPart == '..') {
- array_pop($outputUrlParts);
- unset($inputUrlParts[$key]);
- }
- }
- $outputUrlParts[] = implode('/', $inputUrlParts);
- } else {
- $outputUrlParts[] = $inputUrl;
- }
- $outputUrl = implode('/', $outputUrlParts);
-
- return str_replace($inputUrl, $outputUrl, $fullMatch);
- };
-
- $cssContent = preg_replace_callback('/url\(["\']?([^)^"^\']*)["\']?\)/is', $callback, $cssContent);
-
- return $cssContent;
- }
-
- /**
- * Creates template of configuration file for [[actionCompress]].
- * @param string $configFile output file name.
- * @throws \yii\console\Exception on failure.
- */
- public function actionTemplate($configFile)
- {
- $template = <<jsCompressor)) {
+ $tmpFile = $outputFile . '.tmp';
+ $this->combineJsFiles($inputFiles, $tmpFile);
+ echo shell_exec(strtr($this->jsCompressor, [
+ '{from}' => escapeshellarg($tmpFile),
+ '{to}' => escapeshellarg($outputFile),
+ ]));
+ @unlink($tmpFile);
+ } else {
+ call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile);
+ }
+ if (!file_exists($outputFile)) {
+ throw new Exception("Unable to compress JavaScript files into '{$outputFile}'.");
+ }
+ echo " JavaScript files compressed into '{$outputFile}'.\n";
+ }
+
+ /**
+ * Compresses given CSS files and combines them into the single one.
+ * @param array $inputFiles list of source file names.
+ * @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure
+ */
+ protected function compressCssFiles($inputFiles, $outputFile)
+ {
+ if (empty($inputFiles)) {
+ return;
+ }
+ echo " Compressing CSS files...\n";
+ if (is_string($this->cssCompressor)) {
+ $tmpFile = $outputFile . '.tmp';
+ $this->combineCssFiles($inputFiles, $tmpFile);
+ echo shell_exec(strtr($this->cssCompressor, [
+ '{from}' => escapeshellarg($tmpFile),
+ '{to}' => escapeshellarg($outputFile),
+ ]));
+ @unlink($tmpFile);
+ } else {
+ call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile);
+ }
+ if (!file_exists($outputFile)) {
+ throw new Exception("Unable to compress CSS files into '{$outputFile}'.");
+ }
+ echo " CSS files compressed into '{$outputFile}'.\n";
+ }
+
+ /**
+ * Combines JavaScript files into a single one.
+ * @param array $inputFiles source file names.
+ * @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ public function combineJsFiles($inputFiles, $outputFile)
+ {
+ $content = '';
+ foreach ($inputFiles as $file) {
+ $content .= "/*** BEGIN FILE: $file ***/\n"
+ . file_get_contents($file)
+ . "/*** END FILE: $file ***/\n";
+ }
+ if (!file_put_contents($outputFile, $content)) {
+ throw new Exception("Unable to write output JavaScript file '{$outputFile}'.");
+ }
+ }
+
+ /**
+ * Combines CSS files into a single one.
+ * @param array $inputFiles source file names.
+ * @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ public function combineCssFiles($inputFiles, $outputFile)
+ {
+ $content = '';
+ foreach ($inputFiles as $file) {
+ $content .= "/*** BEGIN FILE: $file ***/\n"
+ . $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile))
+ . "/*** END FILE: $file ***/\n";
+ }
+ if (!file_put_contents($outputFile, $content)) {
+ throw new Exception("Unable to write output CSS file '{$outputFile}'.");
+ }
+ }
+
+ /**
+ * Adjusts CSS content allowing URL references pointing to the original resources.
+ * @param string $cssContent source CSS content.
+ * @param string $inputFilePath input CSS file name.
+ * @param string $outputFilePath output CSS file name.
+ * @return string adjusted CSS content.
+ */
+ protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath)
+ {
+ $sharedPathParts = [];
+ $inputFilePathParts = explode('/', $inputFilePath);
+ $inputFilePathPartsCount = count($inputFilePathParts);
+ $outputFilePathParts = explode('/', $outputFilePath);
+ $outputFilePathPartsCount = count($outputFilePathParts);
+ for ($i =0; $i < $inputFilePathPartsCount && $i < $outputFilePathPartsCount; $i++) {
+ if ($inputFilePathParts[$i] == $outputFilePathParts[$i]) {
+ $sharedPathParts[] = $inputFilePathParts[$i];
+ } else {
+ break;
+ }
+ }
+ $sharedPath = implode('/', $sharedPathParts);
+
+ $inputFileRelativePath = trim(str_replace($sharedPath, '', $inputFilePath), '/');
+ $outputFileRelativePath = trim(str_replace($sharedPath, '', $outputFilePath), '/');
+ $inputFileRelativePathParts = explode('/', $inputFileRelativePath);
+ $outputFileRelativePathParts = explode('/', $outputFileRelativePath);
+
+ $callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
+ $fullMatch = $matches[0];
+ $inputUrl = $matches[1];
+
+ if (preg_match('/https?:\/\//is', $inputUrl)) {
+ return $fullMatch;
+ }
+
+ $outputUrlParts = array_fill(0, count($outputFileRelativePathParts), '..');
+ $outputUrlParts = array_merge($outputUrlParts, $inputFileRelativePathParts);
+
+ if (strpos($inputUrl, '/') !== false) {
+ $inputUrlParts = explode('/', $inputUrl);
+ foreach ($inputUrlParts as $key => $inputUrlPart) {
+ if ($inputUrlPart == '..') {
+ array_pop($outputUrlParts);
+ unset($inputUrlParts[$key]);
+ }
+ }
+ $outputUrlParts[] = implode('/', $inputUrlParts);
+ } else {
+ $outputUrlParts[] = $inputUrl;
+ }
+ $outputUrl = implode('/', $outputUrlParts);
+
+ return str_replace($inputUrl, $outputUrl, $fullMatch);
+ };
+
+ $cssContent = preg_replace_callback('/url\(["\']?([^)^"^\']*)["\']?\)/is', $callback, $cssContent);
+
+ return $cssContent;
+ }
+
+ /**
+ * Creates template of configuration file for [[actionCompress]].
+ * @param string $configFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ public function actionTemplate($configFile)
+ {
+ $template = << [
- // 'yii\web\YiiAsset',
- // 'yii\web\JqueryAsset',
- ],
- // Asset bundle for compression output:
- 'targets' => [
- 'app\assets\AllAsset' => [
- 'basePath' => 'path/to/web',
- 'baseUrl' => '',
- 'js' => 'js/all-{ts}.js',
- 'css' => 'css/all-{ts}.css',
- ],
- ],
- // Asset manager configuration:
- 'assetManager' => [
- 'basePath' => __DIR__,
- 'baseUrl' => '',
- ],
+ // The list of asset bundles to compress:
+ 'bundles' => [
+ // 'yii\web\YiiAsset',
+ // 'yii\web\JqueryAsset',
+ ],
+ // Asset bundle for compression output:
+ 'targets' => [
+ 'app\assets\AllAsset' => [
+ 'basePath' => 'path/to/web',
+ 'baseUrl' => '',
+ 'js' => 'js/all-{ts}.js',
+ 'css' => 'css/all-{ts}.css',
+ ],
+ ],
+ // Asset manager configuration:
+ 'assetManager' => [
+ 'basePath' => __DIR__,
+ 'baseUrl' => '',
+ ],
];
EOD;
- if (file_exists($configFile)) {
- if (!$this->confirm("File '{$configFile}' already exists. Do you wish to overwrite it?")) {
- return;
- }
- }
- if (!file_put_contents($configFile, $template)) {
- throw new Exception("Unable to write template file '{$configFile}'.");
- } else {
- echo "Configuration file template created at '{$configFile}'.\n\n";
- }
- }
+ if (file_exists($configFile)) {
+ if (!$this->confirm("File '{$configFile}' already exists. Do you wish to overwrite it?")) {
+ return;
+ }
+ }
+ if (!file_put_contents($configFile, $template)) {
+ throw new Exception("Unable to write template file '{$configFile}'.");
+ } else {
+ echo "Configuration file template created at '{$configFile}'.\n\n";
+ }
+ }
}
diff --git a/framework/console/controllers/CacheController.php b/framework/console/controllers/CacheController.php
index 1eebc5ad549..0fcc047fa53 100644
--- a/framework/console/controllers/CacheController.php
+++ b/framework/console/controllers/CacheController.php
@@ -20,48 +20,48 @@
*/
class CacheController extends Controller
{
- /**
- * Lists the caches that can be flushed.
- */
- public function actionIndex()
- {
- $caches = [];
- $components = Yii::$app->getComponents();
- foreach ($components as $name => $component) {
- if ($component instanceof Cache) {
- $caches[$name] = get_class($component);
- } elseif (is_array($component) && isset($component['class']) && strpos($component['class'], 'Cache') !== false) {
- $caches[$name] = $component['class'];
- }
- }
- if (!empty($caches)) {
- echo "The following caches can be flushed:\n\n";
- foreach ($caches as $name => $class) {
- echo " * $name: $class\n";
- }
- } else {
- echo "No cache is used.\n";
- }
- }
+ /**
+ * Lists the caches that can be flushed.
+ */
+ public function actionIndex()
+ {
+ $caches = [];
+ $components = Yii::$app->getComponents();
+ foreach ($components as $name => $component) {
+ if ($component instanceof Cache) {
+ $caches[$name] = get_class($component);
+ } elseif (is_array($component) && isset($component['class']) && strpos($component['class'], 'Cache') !== false) {
+ $caches[$name] = $component['class'];
+ }
+ }
+ if (!empty($caches)) {
+ echo "The following caches can be flushed:\n\n";
+ foreach ($caches as $name => $class) {
+ echo " * $name: $class\n";
+ }
+ } else {
+ echo "No cache is used.\n";
+ }
+ }
- /**
- * Flushes cache.
- * @param string $component Name of the cache application component to use.
- *
- * @throws \yii\console\Exception
- */
- public function actionFlush($component = 'cache')
- {
- /** @var Cache $cache */
- $cache = Yii::$app->getComponent($component);
- if (!$cache || !$cache instanceof Cache) {
- throw new Exception('Application component "'.$component.'" is not defined or not a cache.');
- }
+ /**
+ * Flushes cache.
+ * @param string $component Name of the cache application component to use.
+ *
+ * @throws \yii\console\Exception
+ */
+ public function actionFlush($component = 'cache')
+ {
+ /** @var Cache $cache */
+ $cache = Yii::$app->getComponent($component);
+ if (!$cache || !$cache instanceof Cache) {
+ throw new Exception('Application component "'.$component.'" is not defined or not a cache.');
+ }
- if (!$cache->flush()) {
- throw new Exception('Unable to flush cache.');
- }
+ if (!$cache->flush()) {
+ throw new Exception('Unable to flush cache.');
+ }
- echo "\nDone.\n";
- }
+ echo "\nDone.\n";
+ }
}
diff --git a/framework/console/controllers/FixtureController.php b/framework/console/controllers/FixtureController.php
index 24989ce0135..4a21054b177 100644
--- a/framework/console/controllers/FixtureController.php
+++ b/framework/console/controllers/FixtureController.php
@@ -34,288 +34,288 @@
class FixtureController extends Controller
{
- use FixtureTrait;
-
- /**
- * type of fixture apply to database
- */
- const APPLY_ALL = 'all';
-
- /**
- * @var string controller default action ID.
- */
- public $defaultAction = 'load';
- /**
- * @var string default namespace to search fixtures in
- */
- public $namespace = 'tests\unit\fixtures';
- /**
- * @var array global fixtures that should be applied when loading and unloading. By default it is set to `InitDbFixture`
- * that disables and enables integrity check, so your data can be safely loaded.
- */
- public $globalFixtures = [
- 'yii\test\InitDb',
- ];
-
- /**
- * Returns the names of the global options for this command.
- * @return array the names of the global options for this command.
- */
- public function options($id)
- {
- return array_merge(parent::options($id), [
- 'namespace', 'globalFixtures'
- ]);
- }
-
- /**
- * Loads given fixture. You can load several fixtures specifying
- * their names separated with commas, like: User,UserProfile,MyCustom. Be sure there is no
- * whitespace between names. Note that if you are loading fixtures to storage, for example: database or nosql,
- * storage will not be cleared, data will be appended to already existed.
- * @param array $fixtures
- * @param array $except
- * @throws \yii\console\Exception
- */
- public function actionLoad(array $fixtures, array $except = [])
- {
- $foundFixtures = $this->findFixtures($fixtures);
-
- if (!$this->needToApplyAll($fixtures[0])) {
- $notFoundFixtures = array_diff($fixtures, $foundFixtures);
-
- if ($notFoundFixtures) {
- $this->notifyNotFound($notFoundFixtures);
- }
- }
-
- if (!$foundFixtures) {
- throw new Exception("No files were found by name: \"" . implode(', ', $fixtures) . "\".\n"
- . "Check that files with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"."
- );
- }
-
- if (!$this->confirmLoad($foundFixtures, $except)) {
- return;
- }
-
- $filtered = array_diff($foundFixtures, $except);
- $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $filtered));
-
- if (!$fixtures) {
- throw new Exception('No fixtures were found in namespace: "' . $this->namespace . '"' . '');
- }
-
- $fixturesObjects = $this->createFixtures($fixtures);
- $this->unloadFixtures($fixturesObjects);
- $this->loadFixtures($fixturesObjects);
- $this->notifyLoaded($fixtures);
- }
-
- /**
- * Unloads given fixtures. You can clear environment and unload multiple fixtures by specifying
- * their names separated with commas, like: User,UserProfile,MyCustom. Be sure there is no
- * whitespace between names.
- * @param array|string $fixtures
- * @param array|string $except
- */
- public function actionUnload(array $fixtures, array $except = [])
- {
- $foundFixtures = $this->findFixtures($fixtures);
-
- if (!$this->needToApplyAll($fixtures[0])) {
- $notFoundFixtures = array_diff($fixtures, $foundFixtures);
-
- if ($notFoundFixtures) {
- $this->notifyNotFound($notFoundFixtures);
- }
- }
-
- if (!$foundFixtures) {
- throw new Exception("No files were found by name: \"" . implode(', ', $fixtures) . "\".\n"
- . "Check that fixtures with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"."
- );
- }
-
- if (!$this->confirmUnload($foundFixtures, $except)) {
- return;
- }
-
- $filtered = array_diff($foundFixtures, $except);
- $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $filtered));
-
- if (!$fixtures) {
- throw new Exception('No fixtures were found in namespace: ' . $this->namespace . '".');
- }
-
- $this->unloadFixtures($this->createFixtures($fixtures));
- $this->notifyUnloaded($fixtures);
- }
-
- /**
- * Notifies user that fixtures were successfully loaded.
- * @param array $fixtures
- */
- private function notifyLoaded($fixtures)
- {
- $this->stdout("Fixtures were successfully loaded from namespace:\n", Console::FG_YELLOW);
- $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
- $this->outputList($fixtures);
- }
-
- /**
- * Notifies user that fixtures were successfully unloaded.
- * @param array $fixtures
- */
- private function notifyUnloaded($fixtures)
- {
- $this->stdout("Fixtures were successfully unloaded from namespace:\n", Console::FG_YELLOW);
- $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
- $this->outputList($fixtures);
- }
-
- /**
- * Notifies user that fixtures were not found under fixtures path.
- * @param array $fixtures
- */
- private function notifyNotFound($fixtures)
- {
- $this->stdout("Some fixtures were not found under path:\n", Console::BG_RED);
- $this->stdout("\t" . $this->getFixturePath() . "\n\n", Console::FG_GREEN);
- $this->stdout("Check that they have correct namespace \"{$this->namespace}\" \n", Console::BG_RED);
- $this->outputList($fixtures);
- $this->stdout("\n");
- }
-
- /**
- * Prompts user with confirmation if fixtures should be loaded.
- * @param array $fixtures
- * @param array $except
- * @return boolean
- */
- private function confirmLoad($fixtures, $except)
- {
- $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
- $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);
-
- if (count($this->globalFixtures)) {
- $this->stdout("Global fixtures will be loaded:\n\n", Console::FG_YELLOW);
- $this->outputList($this->globalFixtures);
- }
-
- $this->stdout("\nFixtures below will be loaded:\n\n", Console::FG_YELLOW);
- $this->outputList($fixtures);
-
- if (count($except)) {
- $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
- $this->outputList($except);
- }
-
- return $this->confirm("\nLoad above fixtures?");
- }
-
- /**
- * Prompts user with confirmation for fixtures that should be unloaded.
- * @param array $fixtures
- * @param array $except
- * @return boolean
- */
- private function confirmUnload($fixtures, $except)
- {
- $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
- $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);
-
- if (count($this->globalFixtures)) {
- $this->stdout("Global fixtures will be unloaded:\n\n", Console::FG_YELLOW);
- $this->outputList($this->globalFixtures);
- }
-
- $this->stdout("\nFixtures below will be unloaded:\n\n", Console::FG_YELLOW);
- $this->outputList($fixtures);
-
- if (count($except)) {
- $this->stdout("\nFixtures that will NOT be unloaded:\n\n", Console::FG_YELLOW);
- $this->outputList($except);
- }
-
- return $this->confirm("\nUnload fixtures?");
- }
-
- /**
- * Outputs data to the console as a list.
- * @param array $data
- */
- private function outputList($data)
- {
- foreach ($data as $index => $item) {
- $this->stdout("\t" . ($index + 1) . ". {$item}\n", Console::FG_GREEN);
- }
- }
-
- /**
- * Checks if needed to apply all fixtures.
- * @param string $fixture
- * @return bool
- */
- public function needToApplyAll($fixture)
- {
- return $fixture == self::APPLY_ALL;
- }
-
- /**
- * @param array $fixtures
- * @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists.
- */
- private function findFixtures(array $fixtures)
- {
- $fixturesPath = $this->getFixturePath();
-
- $filesToSearch = ['*Fixture.php'];
- if (!$this->needToApplyAll($fixtures[0])) {
- $filesToSearch = [];
- foreach ($fixtures as $fileName) {
- $filesToSearch[] = $fileName . 'Fixture.php';
- }
- }
-
- $files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]);
- $foundFixtures = [];
-
- foreach ($files as $fixture) {
- $foundFixtures[] = basename($fixture, 'Fixture.php');
- }
-
- return $foundFixtures;
- }
-
- /**
- * Returns valid fixtures config that can be used to load them.
- * @param array $fixtures fixtures to configure
- * @return array
- */
- private function getFixturesConfig($fixtures)
- {
- $config = [];
-
- foreach ($fixtures as $fixture) {
-
- $isNamespaced = (strpos($fixture, '\\') !== false);
- $fullClassName = $isNamespaced ? $fixture . 'Fixture' : $this->namespace . '\\' . $fixture . 'Fixture';
-
- if (class_exists($fullClassName)) {
- $config[] = $fullClassName;
- }
- }
-
- return $config;
- }
-
- /**
- * Returns fixture path that determined on fixtures namespace.
- * @return string fixture path
- */
- private function getFixturePath()
- {
- return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace));
- }
+ use FixtureTrait;
+
+ /**
+ * type of fixture apply to database
+ */
+ const APPLY_ALL = 'all';
+
+ /**
+ * @var string controller default action ID.
+ */
+ public $defaultAction = 'load';
+ /**
+ * @var string default namespace to search fixtures in
+ */
+ public $namespace = 'tests\unit\fixtures';
+ /**
+ * @var array global fixtures that should be applied when loading and unloading. By default it is set to `InitDbFixture`
+ * that disables and enables integrity check, so your data can be safely loaded.
+ */
+ public $globalFixtures = [
+ 'yii\test\InitDb',
+ ];
+
+ /**
+ * Returns the names of the global options for this command.
+ * @return array the names of the global options for this command.
+ */
+ public function options($id)
+ {
+ return array_merge(parent::options($id), [
+ 'namespace', 'globalFixtures'
+ ]);
+ }
+
+ /**
+ * Loads given fixture. You can load several fixtures specifying
+ * their names separated with commas, like: User,UserProfile,MyCustom. Be sure there is no
+ * whitespace between names. Note that if you are loading fixtures to storage, for example: database or nosql,
+ * storage will not be cleared, data will be appended to already existed.
+ * @param array $fixtures
+ * @param array $except
+ * @throws \yii\console\Exception
+ */
+ public function actionLoad(array $fixtures, array $except = [])
+ {
+ $foundFixtures = $this->findFixtures($fixtures);
+
+ if (!$this->needToApplyAll($fixtures[0])) {
+ $notFoundFixtures = array_diff($fixtures, $foundFixtures);
+
+ if ($notFoundFixtures) {
+ $this->notifyNotFound($notFoundFixtures);
+ }
+ }
+
+ if (!$foundFixtures) {
+ throw new Exception("No files were found by name: \"" . implode(', ', $fixtures) . "\".\n"
+ . "Check that files with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"."
+ );
+ }
+
+ if (!$this->confirmLoad($foundFixtures, $except)) {
+ return;
+ }
+
+ $filtered = array_diff($foundFixtures, $except);
+ $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $filtered));
+
+ if (!$fixtures) {
+ throw new Exception('No fixtures were found in namespace: "' . $this->namespace . '"' . '');
+ }
+
+ $fixturesObjects = $this->createFixtures($fixtures);
+ $this->unloadFixtures($fixturesObjects);
+ $this->loadFixtures($fixturesObjects);
+ $this->notifyLoaded($fixtures);
+ }
+
+ /**
+ * Unloads given fixtures. You can clear environment and unload multiple fixtures by specifying
+ * their names separated with commas, like: User,UserProfile,MyCustom. Be sure there is no
+ * whitespace between names.
+ * @param array|string $fixtures
+ * @param array|string $except
+ */
+ public function actionUnload(array $fixtures, array $except = [])
+ {
+ $foundFixtures = $this->findFixtures($fixtures);
+
+ if (!$this->needToApplyAll($fixtures[0])) {
+ $notFoundFixtures = array_diff($fixtures, $foundFixtures);
+
+ if ($notFoundFixtures) {
+ $this->notifyNotFound($notFoundFixtures);
+ }
+ }
+
+ if (!$foundFixtures) {
+ throw new Exception("No files were found by name: \"" . implode(', ', $fixtures) . "\".\n"
+ . "Check that fixtures with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"."
+ );
+ }
+
+ if (!$this->confirmUnload($foundFixtures, $except)) {
+ return;
+ }
+
+ $filtered = array_diff($foundFixtures, $except);
+ $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $filtered));
+
+ if (!$fixtures) {
+ throw new Exception('No fixtures were found in namespace: ' . $this->namespace . '".');
+ }
+
+ $this->unloadFixtures($this->createFixtures($fixtures));
+ $this->notifyUnloaded($fixtures);
+ }
+
+ /**
+ * Notifies user that fixtures were successfully loaded.
+ * @param array $fixtures
+ */
+ private function notifyLoaded($fixtures)
+ {
+ $this->stdout("Fixtures were successfully loaded from namespace:\n", Console::FG_YELLOW);
+ $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
+ $this->outputList($fixtures);
+ }
+
+ /**
+ * Notifies user that fixtures were successfully unloaded.
+ * @param array $fixtures
+ */
+ private function notifyUnloaded($fixtures)
+ {
+ $this->stdout("Fixtures were successfully unloaded from namespace:\n", Console::FG_YELLOW);
+ $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
+ $this->outputList($fixtures);
+ }
+
+ /**
+ * Notifies user that fixtures were not found under fixtures path.
+ * @param array $fixtures
+ */
+ private function notifyNotFound($fixtures)
+ {
+ $this->stdout("Some fixtures were not found under path:\n", Console::BG_RED);
+ $this->stdout("\t" . $this->getFixturePath() . "\n\n", Console::FG_GREEN);
+ $this->stdout("Check that they have correct namespace \"{$this->namespace}\" \n", Console::BG_RED);
+ $this->outputList($fixtures);
+ $this->stdout("\n");
+ }
+
+ /**
+ * Prompts user with confirmation if fixtures should be loaded.
+ * @param array $fixtures
+ * @param array $except
+ * @return boolean
+ */
+ private function confirmLoad($fixtures, $except)
+ {
+ $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
+ $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);
+
+ if (count($this->globalFixtures)) {
+ $this->stdout("Global fixtures will be loaded:\n\n", Console::FG_YELLOW);
+ $this->outputList($this->globalFixtures);
+ }
+
+ $this->stdout("\nFixtures below will be loaded:\n\n", Console::FG_YELLOW);
+ $this->outputList($fixtures);
+
+ if (count($except)) {
+ $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
+ $this->outputList($except);
+ }
+
+ return $this->confirm("\nLoad above fixtures?");
+ }
+
+ /**
+ * Prompts user with confirmation for fixtures that should be unloaded.
+ * @param array $fixtures
+ * @param array $except
+ * @return boolean
+ */
+ private function confirmUnload($fixtures, $except)
+ {
+ $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
+ $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);
+
+ if (count($this->globalFixtures)) {
+ $this->stdout("Global fixtures will be unloaded:\n\n", Console::FG_YELLOW);
+ $this->outputList($this->globalFixtures);
+ }
+
+ $this->stdout("\nFixtures below will be unloaded:\n\n", Console::FG_YELLOW);
+ $this->outputList($fixtures);
+
+ if (count($except)) {
+ $this->stdout("\nFixtures that will NOT be unloaded:\n\n", Console::FG_YELLOW);
+ $this->outputList($except);
+ }
+
+ return $this->confirm("\nUnload fixtures?");
+ }
+
+ /**
+ * Outputs data to the console as a list.
+ * @param array $data
+ */
+ private function outputList($data)
+ {
+ foreach ($data as $index => $item) {
+ $this->stdout("\t" . ($index + 1) . ". {$item}\n", Console::FG_GREEN);
+ }
+ }
+
+ /**
+ * Checks if needed to apply all fixtures.
+ * @param string $fixture
+ * @return bool
+ */
+ public function needToApplyAll($fixture)
+ {
+ return $fixture == self::APPLY_ALL;
+ }
+
+ /**
+ * @param array $fixtures
+ * @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists.
+ */
+ private function findFixtures(array $fixtures)
+ {
+ $fixturesPath = $this->getFixturePath();
+
+ $filesToSearch = ['*Fixture.php'];
+ if (!$this->needToApplyAll($fixtures[0])) {
+ $filesToSearch = [];
+ foreach ($fixtures as $fileName) {
+ $filesToSearch[] = $fileName . 'Fixture.php';
+ }
+ }
+
+ $files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]);
+ $foundFixtures = [];
+
+ foreach ($files as $fixture) {
+ $foundFixtures[] = basename($fixture, 'Fixture.php');
+ }
+
+ return $foundFixtures;
+ }
+
+ /**
+ * Returns valid fixtures config that can be used to load them.
+ * @param array $fixtures fixtures to configure
+ * @return array
+ */
+ private function getFixturesConfig($fixtures)
+ {
+ $config = [];
+
+ foreach ($fixtures as $fixture) {
+
+ $isNamespaced = (strpos($fixture, '\\') !== false);
+ $fullClassName = $isNamespaced ? $fixture . 'Fixture' : $this->namespace . '\\' . $fixture . 'Fixture';
+
+ if (class_exists($fullClassName)) {
+ $config[] = $fullClassName;
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * Returns fixture path that determined on fixtures namespace.
+ * @return string fixture path
+ */
+ private function getFixturePath()
+ {
+ return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace));
+ }
}
diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php
index 99a5846c066..694113c864f 100644
--- a/framework/console/controllers/HelpController.php
+++ b/framework/console/controllers/HelpController.php
@@ -38,434 +38,441 @@
*/
class HelpController extends Controller
{
- /**
- * Displays available commands or the detailed information
- * about a particular command. For example,
- *
- * @param string $command The name of the command to show help about.
- * If not provided, all available commands will be displayed.
- * @return integer the exit status
- * @throws Exception if the command for help is unknown
- */
- public function actionIndex($command = null)
- {
- if ($command !== null) {
- $result = Yii::$app->createController($command);
- if ($result === false) {
- throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', [
- 'command' => $this->ansiFormat($command, Console::FG_YELLOW),
- ]));
- }
-
- list($controller, $actionID) = $result;
-
- $actions = $this->getActions($controller);
- if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
- $this->getActionHelp($controller, $actionID);
- } else {
- $this->getControllerHelp($controller);
- }
- } else {
- $this->getHelp();
- }
- }
-
- /**
- * Returns all available command names.
- * @return array all available command names
- */
- public function getCommands()
- {
- $commands = $this->getModuleCommands(Yii::$app);
- sort($commands);
- return array_unique($commands);
- }
-
- /**
- * Returns an array of commands an their descriptions.
- * @return array all available commands as keys and their description as values.
- */
- protected function getCommandDescriptions()
- {
- $descriptions = [];
- foreach ($this->getCommands() as $command) {
- $description = '';
-
- $result = Yii::$app->createController($command);
- if ($result !== false) {
- list($controller, $actionID) = $result;
- $class = new \ReflectionClass($controller);
-
- $docLines = preg_split('~(\n|\r|\r\n)~', $class->getDocComment());
- if (isset($docLines[1])) {
- $description = trim($docLines[1], ' *');
- }
- }
-
- $descriptions[$command] = $description;
- }
- return $descriptions;
- }
-
- /**
- * Returns all available actions of the specified controller.
- * @param Controller $controller the controller instance
- * @return array all available action IDs.
- */
- public function getActions($controller)
- {
- $actions = array_keys($controller->actions());
- $class = new \ReflectionClass($controller);
- foreach ($class->getMethods() as $method) {
- $name = $method->getName();
- if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
- $actions[] = Inflector::camel2id(substr($name, 6));
- }
- }
- sort($actions);
- return array_unique($actions);
- }
-
- /**
- * Returns available commands of a specified module.
- * @param \yii\base\Module $module the module instance
- * @return array the available command names
- */
- protected function getModuleCommands($module)
- {
- $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';
-
- $commands = [];
- foreach (array_keys($module->controllerMap) as $id) {
- $commands[] = $prefix . $id;
- }
-
- foreach ($module->getModules() as $id => $child) {
- if (($child = $module->getModule($id)) === null) {
- continue;
- }
- foreach ($this->getModuleCommands($child) as $command) {
- $commands[] = $command;
- }
- }
-
- $controllerPath = $module->getControllerPath();
- if (is_dir($controllerPath)) {
- $files = scandir($controllerPath);
- foreach ($files as $file) {
- if (strcmp(substr($file, -14), 'Controller.php') === 0) {
- $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
- }
- }
- }
-
- return $commands;
- }
-
- /**
- * Displays all available commands.
- */
- protected function getHelp()
- {
- $commands = $this->getCommandDescriptions();
- if (!empty($commands)) {
- $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
- $len = 0;
- foreach ($commands as $command => $description) {
- if (($l = strlen($command)) > $len) {
- $len = $l;
- }
- }
- foreach ($commands as $command => $description) {
- echo "- " . $this->ansiFormat($command, Console::FG_YELLOW);
- echo str_repeat(' ', $len + 3 - strlen($command)) . $description;
- echo "\n";
- }
- $scriptName = $this->getScriptName();
- $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
- echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
- . $this->ansiFormat('', Console::FG_CYAN) . "\n\n";
- } else {
- $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
- }
- }
-
- /**
- * Displays the overall information of the command.
- * @param Controller $controller the controller instance
- */
- protected function getControllerHelp($controller)
- {
- $class = new \ReflectionClass($controller);
- $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($class->getDocComment(), '/'))), "\r", '');
- if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
- $comment = trim(substr($comment, 0, $matches[0][1]));
- }
-
- if ($comment !== '') {
- $this->stdout("\nDESCRIPTION\n", Console::BOLD);
- echo "\n" . Console::renderColoredString($comment) . "\n\n";
- }
-
- $actions = $this->getActions($controller);
- if (!empty($actions)) {
- $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
- $prefix = $controller->getUniqueId();
- foreach ($actions as $action) {
- echo '- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW);
- if ($action === $controller->defaultAction) {
- $this->stdout(' (default)', Console::FG_GREEN);
- }
- $summary = $this->getActionSummary($controller, $action);
- if ($summary !== '') {
- echo ': ' . $summary;
- }
- echo "\n";
- }
- $scriptName = $this->getScriptName();
- echo "\nTo see the detailed information about individual sub-commands, enter:\n";
- echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
- . $this->ansiFormat('', Console::FG_CYAN) . "\n\n";
- }
- }
-
- /**
- * Returns the short summary of the action.
- * @param Controller $controller the controller instance
- * @param string $actionID action ID
- * @return string the summary about the action
- */
- protected function getActionSummary($controller, $actionID)
- {
- $action = $controller->createAction($actionID);
- if ($action === null) {
- return '';
- }
- if ($action instanceof InlineAction) {
- $reflection = new \ReflectionMethod($controller, $action->actionMethod);
- } else {
- $reflection = new \ReflectionClass($action);
- }
- $tags = $this->parseComment($reflection->getDocComment());
- if ($tags['description'] !== '') {
- $limit = 73 - strlen($action->getUniqueId());
- if ($actionID === $controller->defaultAction) {
- $limit -= 10;
- }
- if ($limit < 0) {
- $limit = 50;
- }
- $description = $tags['description'];
- if (($pos = strpos($tags['description'], "\n")) !== false) {
- $description = substr($description, 0, $pos);
- }
- $text = substr($description, 0, $limit);
- return strlen($description) > $limit ? $text . '...' : $text;
- } else {
- return '';
- }
- }
-
- /**
- * Displays the detailed information of a command action.
- * @param Controller $controller the controller instance
- * @param string $actionID action ID
- * @throws Exception if the action does not exist
- */
- protected function getActionHelp($controller, $actionID)
- {
- $action = $controller->createAction($actionID);
- if ($action === null) {
- throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', [
- 'command' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'),
- ]));
- }
- if ($action instanceof InlineAction) {
- $method = new \ReflectionMethod($controller, $action->actionMethod);
- } else {
- $method = new \ReflectionMethod($action, 'run');
- }
-
- $tags = $this->parseComment($method->getDocComment());
- $options = $this->getOptionHelps($controller, $actionID);
-
- if ($tags['description'] !== '') {
- $this->stdout("\nDESCRIPTION\n", Console::BOLD);
- echo "\n" . Console::renderColoredString($tags['description']) . "\n\n";
- }
-
- $this->stdout("\nUSAGE\n\n", Console::BOLD);
- $scriptName = $this->getScriptName();
- if ($action->id === $controller->defaultAction) {
- echo $scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW);
- } else {
- echo $scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW);
- }
- list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : []);
- foreach ($required as $arg => $description) {
- $this->stdout(' <' . $arg . '>', Console::FG_CYAN);
- }
- foreach ($optional as $arg => $description) {
- $this->stdout(' [' . $arg . ']', Console::FG_CYAN);
- }
- if (!empty($options)) {
- $this->stdout(' [...options...]', Console::FG_RED);
- }
- echo "\n\n";
-
- if (!empty($required) || !empty($optional)) {
- echo implode("\n\n", array_merge($required, $optional)) . "\n\n";
- }
-
- if (!empty($options)) {
- $this->stdout("\nOPTIONS\n\n", Console::BOLD);
- echo implode("\n\n", $options) . "\n\n";
- }
- }
-
- /**
- * Returns the help information about arguments.
- * @param \ReflectionMethod $method
- * @param string $tags the parsed comment block related with arguments
- * @return array the required and optional argument help information
- */
- protected function getArgHelps($method, $tags)
- {
- if (is_string($tags)) {
- $tags = [$tags];
- }
- $params = $method->getParameters();
- $optional = $required = [];
- foreach ($params as $i => $param) {
- $name = $param->getName();
- $tag = isset($tags[$i]) ? $tags[$i] : '';
- if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
- $type = $matches[1];
- $comment = $matches[3];
- } else {
- $type = null;
- $comment = $tag;
- }
- if ($param->isDefaultValueAvailable()) {
- $optional[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), false, $type, $param->getDefaultValue(), $comment);
- } else {
- $required[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), true, $type, null, $comment);
- }
- }
-
- return [$required, $optional];
- }
-
- /**
- * Returns the help information about the options available for a console controller.
- * @param Controller $controller the console controller
- * @param string $actionID name of the action, if set include local options for that action
- * @return array the help information about the options
- */
- protected function getOptionHelps($controller, $actionID)
- {
- $optionNames = $controller->options($actionID);
- if (empty($optionNames)) {
- return [];
- }
-
- $class = new \ReflectionClass($controller);
- $options = [];
- foreach ($class->getProperties() as $property) {
- $name = $property->getName();
- if (!in_array($name, $optionNames, true)) {
- continue;
- }
- $defaultValue = $property->getValue($controller);
- $tags = $this->parseComment($property->getDocComment());
- if (isset($tags['var']) || isset($tags['property'])) {
- $doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
- if (is_array($doc)) {
- $doc = reset($doc);
- }
- if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) {
- $type = $matches[1];
- $comment = $matches[2];
- } else {
- $type = null;
- $comment = $doc;
- }
- $options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, $type, $defaultValue, $comment);
- } else {
- $options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, null, $defaultValue, '');
- }
- }
- ksort($options);
- return $options;
- }
-
- /**
- * Parses the comment block into tags.
- * @param string $comment the comment block
- * @return array the parsed tags
- */
- protected function parseComment($comment)
- {
- $tags = [];
- $comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
- $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
- foreach ($parts as $part) {
- if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
- $name = $matches[1];
- if (!isset($tags[$name])) {
- $tags[$name] = trim($matches[2]);
- } elseif (is_array($tags[$name])) {
- $tags[$name][] = trim($matches[2]);
- } else {
- $tags[$name] = [$tags[$name], trim($matches[2])];
- }
- }
- }
- return $tags;
- }
-
- /**
- * Generates a well-formed string for an argument or option.
- * @param string $name the name of the argument or option
- * @param boolean $required whether the argument is required
- * @param string $type the type of the option or argument
- * @param mixed $defaultValue the default value of the option or argument
- * @param string $comment comment about the option or argument
- * @return string the formatted string for the argument or option
- */
- protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
- {
- $doc = '';
- $comment = trim($comment);
-
- if ($defaultValue !== null && !is_array($defaultValue)) {
- if ($type === null) {
- $type = gettype($defaultValue);
- }
- if (is_bool($defaultValue)) {
- // show as integer to avoid confusion
- $defaultValue = (int)$defaultValue;
- }
- $doc = "$type (defaults to " . var_export($defaultValue, true) . ")";
- } elseif (trim($type) !== '') {
- $doc = $type;
- }
-
- if ($doc === '') {
- $doc = $comment;
- } elseif ($comment !== '') {
- $doc .= "\n" . preg_replace("/^/m", " ", $comment);
- }
-
- $name = $required ? "$name (required)" : $name;
- return $doc === '' ? $name : "$name: $doc";
- }
-
- /**
- * @return string the name of the cli script currently running.
- */
- protected function getScriptName()
- {
- return basename(Yii::$app->request->scriptFile);
- }
+ /**
+ * Displays available commands or the detailed information
+ * about a particular command. For example,
+ *
+ * @param string $command The name of the command to show help about.
+ * If not provided, all available commands will be displayed.
+ * @return integer the exit status
+ * @throws Exception if the command for help is unknown
+ */
+ public function actionIndex($command = null)
+ {
+ if ($command !== null) {
+ $result = Yii::$app->createController($command);
+ if ($result === false) {
+ throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', [
+ 'command' => $this->ansiFormat($command, Console::FG_YELLOW),
+ ]));
+ }
+
+ list($controller, $actionID) = $result;
+
+ $actions = $this->getActions($controller);
+ if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
+ $this->getActionHelp($controller, $actionID);
+ } else {
+ $this->getControllerHelp($controller);
+ }
+ } else {
+ $this->getHelp();
+ }
+ }
+
+ /**
+ * Returns all available command names.
+ * @return array all available command names
+ */
+ public function getCommands()
+ {
+ $commands = $this->getModuleCommands(Yii::$app);
+ sort($commands);
+
+ return array_unique($commands);
+ }
+
+ /**
+ * Returns an array of commands an their descriptions.
+ * @return array all available commands as keys and their description as values.
+ */
+ protected function getCommandDescriptions()
+ {
+ $descriptions = [];
+ foreach ($this->getCommands() as $command) {
+ $description = '';
+
+ $result = Yii::$app->createController($command);
+ if ($result !== false) {
+ list($controller, $actionID) = $result;
+ $class = new \ReflectionClass($controller);
+
+ $docLines = preg_split('~(\n|\r|\r\n)~', $class->getDocComment());
+ if (isset($docLines[1])) {
+ $description = trim($docLines[1], ' *');
+ }
+ }
+
+ $descriptions[$command] = $description;
+ }
+
+ return $descriptions;
+ }
+
+ /**
+ * Returns all available actions of the specified controller.
+ * @param Controller $controller the controller instance
+ * @return array all available action IDs.
+ */
+ public function getActions($controller)
+ {
+ $actions = array_keys($controller->actions());
+ $class = new \ReflectionClass($controller);
+ foreach ($class->getMethods() as $method) {
+ $name = $method->getName();
+ if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
+ $actions[] = Inflector::camel2id(substr($name, 6));
+ }
+ }
+ sort($actions);
+
+ return array_unique($actions);
+ }
+
+ /**
+ * Returns available commands of a specified module.
+ * @param \yii\base\Module $module the module instance
+ * @return array the available command names
+ */
+ protected function getModuleCommands($module)
+ {
+ $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';
+
+ $commands = [];
+ foreach (array_keys($module->controllerMap) as $id) {
+ $commands[] = $prefix . $id;
+ }
+
+ foreach ($module->getModules() as $id => $child) {
+ if (($child = $module->getModule($id)) === null) {
+ continue;
+ }
+ foreach ($this->getModuleCommands($child) as $command) {
+ $commands[] = $command;
+ }
+ }
+
+ $controllerPath = $module->getControllerPath();
+ if (is_dir($controllerPath)) {
+ $files = scandir($controllerPath);
+ foreach ($files as $file) {
+ if (strcmp(substr($file, -14), 'Controller.php') === 0) {
+ $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
+ }
+ }
+ }
+
+ return $commands;
+ }
+
+ /**
+ * Displays all available commands.
+ */
+ protected function getHelp()
+ {
+ $commands = $this->getCommandDescriptions();
+ if (!empty($commands)) {
+ $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
+ $len = 0;
+ foreach ($commands as $command => $description) {
+ if (($l = strlen($command)) > $len) {
+ $len = $l;
+ }
+ }
+ foreach ($commands as $command => $description) {
+ echo "- " . $this->ansiFormat($command, Console::FG_YELLOW);
+ echo str_repeat(' ', $len + 3 - strlen($command)) . $description;
+ echo "\n";
+ }
+ $scriptName = $this->getScriptName();
+ $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
+ echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
+ . $this->ansiFormat('', Console::FG_CYAN) . "\n\n";
+ } else {
+ $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
+ }
+ }
+
+ /**
+ * Displays the overall information of the command.
+ * @param Controller $controller the controller instance
+ */
+ protected function getControllerHelp($controller)
+ {
+ $class = new \ReflectionClass($controller);
+ $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($class->getDocComment(), '/'))), "\r", '');
+ if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
+ $comment = trim(substr($comment, 0, $matches[0][1]));
+ }
+
+ if ($comment !== '') {
+ $this->stdout("\nDESCRIPTION\n", Console::BOLD);
+ echo "\n" . Console::renderColoredString($comment) . "\n\n";
+ }
+
+ $actions = $this->getActions($controller);
+ if (!empty($actions)) {
+ $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
+ $prefix = $controller->getUniqueId();
+ foreach ($actions as $action) {
+ echo '- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW);
+ if ($action === $controller->defaultAction) {
+ $this->stdout(' (default)', Console::FG_GREEN);
+ }
+ $summary = $this->getActionSummary($controller, $action);
+ if ($summary !== '') {
+ echo ': ' . $summary;
+ }
+ echo "\n";
+ }
+ $scriptName = $this->getScriptName();
+ echo "\nTo see the detailed information about individual sub-commands, enter:\n";
+ echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
+ . $this->ansiFormat('', Console::FG_CYAN) . "\n\n";
+ }
+ }
+
+ /**
+ * Returns the short summary of the action.
+ * @param Controller $controller the controller instance
+ * @param string $actionID action ID
+ * @return string the summary about the action
+ */
+ protected function getActionSummary($controller, $actionID)
+ {
+ $action = $controller->createAction($actionID);
+ if ($action === null) {
+ return '';
+ }
+ if ($action instanceof InlineAction) {
+ $reflection = new \ReflectionMethod($controller, $action->actionMethod);
+ } else {
+ $reflection = new \ReflectionClass($action);
+ }
+ $tags = $this->parseComment($reflection->getDocComment());
+ if ($tags['description'] !== '') {
+ $limit = 73 - strlen($action->getUniqueId());
+ if ($actionID === $controller->defaultAction) {
+ $limit -= 10;
+ }
+ if ($limit < 0) {
+ $limit = 50;
+ }
+ $description = $tags['description'];
+ if (($pos = strpos($tags['description'], "\n")) !== false) {
+ $description = substr($description, 0, $pos);
+ }
+ $text = substr($description, 0, $limit);
+
+ return strlen($description) > $limit ? $text . '...' : $text;
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Displays the detailed information of a command action.
+ * @param Controller $controller the controller instance
+ * @param string $actionID action ID
+ * @throws Exception if the action does not exist
+ */
+ protected function getActionHelp($controller, $actionID)
+ {
+ $action = $controller->createAction($actionID);
+ if ($action === null) {
+ throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', [
+ 'command' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'),
+ ]));
+ }
+ if ($action instanceof InlineAction) {
+ $method = new \ReflectionMethod($controller, $action->actionMethod);
+ } else {
+ $method = new \ReflectionMethod($action, 'run');
+ }
+
+ $tags = $this->parseComment($method->getDocComment());
+ $options = $this->getOptionHelps($controller, $actionID);
+
+ if ($tags['description'] !== '') {
+ $this->stdout("\nDESCRIPTION\n", Console::BOLD);
+ echo "\n" . Console::renderColoredString($tags['description']) . "\n\n";
+ }
+
+ $this->stdout("\nUSAGE\n\n", Console::BOLD);
+ $scriptName = $this->getScriptName();
+ if ($action->id === $controller->defaultAction) {
+ echo $scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW);
+ } else {
+ echo $scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW);
+ }
+ list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : []);
+ foreach ($required as $arg => $description) {
+ $this->stdout(' <' . $arg . '>', Console::FG_CYAN);
+ }
+ foreach ($optional as $arg => $description) {
+ $this->stdout(' [' . $arg . ']', Console::FG_CYAN);
+ }
+ if (!empty($options)) {
+ $this->stdout(' [...options...]', Console::FG_RED);
+ }
+ echo "\n\n";
+
+ if (!empty($required) || !empty($optional)) {
+ echo implode("\n\n", array_merge($required, $optional)) . "\n\n";
+ }
+
+ if (!empty($options)) {
+ $this->stdout("\nOPTIONS\n\n", Console::BOLD);
+ echo implode("\n\n", $options) . "\n\n";
+ }
+ }
+
+ /**
+ * Returns the help information about arguments.
+ * @param \ReflectionMethod $method
+ * @param string $tags the parsed comment block related with arguments
+ * @return array the required and optional argument help information
+ */
+ protected function getArgHelps($method, $tags)
+ {
+ if (is_string($tags)) {
+ $tags = [$tags];
+ }
+ $params = $method->getParameters();
+ $optional = $required = [];
+ foreach ($params as $i => $param) {
+ $name = $param->getName();
+ $tag = isset($tags[$i]) ? $tags[$i] : '';
+ if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
+ $type = $matches[1];
+ $comment = $matches[3];
+ } else {
+ $type = null;
+ $comment = $tag;
+ }
+ if ($param->isDefaultValueAvailable()) {
+ $optional[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), false, $type, $param->getDefaultValue(), $comment);
+ } else {
+ $required[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), true, $type, null, $comment);
+ }
+ }
+
+ return [$required, $optional];
+ }
+
+ /**
+ * Returns the help information about the options available for a console controller.
+ * @param Controller $controller the console controller
+ * @param string $actionID name of the action, if set include local options for that action
+ * @return array the help information about the options
+ */
+ protected function getOptionHelps($controller, $actionID)
+ {
+ $optionNames = $controller->options($actionID);
+ if (empty($optionNames)) {
+ return [];
+ }
+
+ $class = new \ReflectionClass($controller);
+ $options = [];
+ foreach ($class->getProperties() as $property) {
+ $name = $property->getName();
+ if (!in_array($name, $optionNames, true)) {
+ continue;
+ }
+ $defaultValue = $property->getValue($controller);
+ $tags = $this->parseComment($property->getDocComment());
+ if (isset($tags['var']) || isset($tags['property'])) {
+ $doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
+ if (is_array($doc)) {
+ $doc = reset($doc);
+ }
+ if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) {
+ $type = $matches[1];
+ $comment = $matches[2];
+ } else {
+ $type = null;
+ $comment = $doc;
+ }
+ $options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, $type, $defaultValue, $comment);
+ } else {
+ $options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, null, $defaultValue, '');
+ }
+ }
+ ksort($options);
+
+ return $options;
+ }
+
+ /**
+ * Parses the comment block into tags.
+ * @param string $comment the comment block
+ * @return array the parsed tags
+ */
+ protected function parseComment($comment)
+ {
+ $tags = [];
+ $comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
+ $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($parts as $part) {
+ if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
+ $name = $matches[1];
+ if (!isset($tags[$name])) {
+ $tags[$name] = trim($matches[2]);
+ } elseif (is_array($tags[$name])) {
+ $tags[$name][] = trim($matches[2]);
+ } else {
+ $tags[$name] = [$tags[$name], trim($matches[2])];
+ }
+ }
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Generates a well-formed string for an argument or option.
+ * @param string $name the name of the argument or option
+ * @param boolean $required whether the argument is required
+ * @param string $type the type of the option or argument
+ * @param mixed $defaultValue the default value of the option or argument
+ * @param string $comment comment about the option or argument
+ * @return string the formatted string for the argument or option
+ */
+ protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
+ {
+ $doc = '';
+ $comment = trim($comment);
+
+ if ($defaultValue !== null && !is_array($defaultValue)) {
+ if ($type === null) {
+ $type = gettype($defaultValue);
+ }
+ if (is_bool($defaultValue)) {
+ // show as integer to avoid confusion
+ $defaultValue = (int) $defaultValue;
+ }
+ $doc = "$type (defaults to " . var_export($defaultValue, true) . ")";
+ } elseif (trim($type) !== '') {
+ $doc = $type;
+ }
+
+ if ($doc === '') {
+ $doc = $comment;
+ } elseif ($comment !== '') {
+ $doc .= "\n" . preg_replace("/^/m", " ", $comment);
+ }
+
+ $name = $required ? "$name (required)" : $name;
+
+ return $doc === '' ? $name : "$name: $doc";
+ }
+
+ /**
+ * @return string the name of the cli script currently running.
+ */
+ protected function getScriptName()
+ {
+ return basename(Yii::$app->request->scriptFile);
+ }
}
diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php
index 456fc51292c..be135bfa5bb 100644
--- a/framework/console/controllers/MessageController.php
+++ b/framework/console/controllers/MessageController.php
@@ -35,345 +35,345 @@
*/
class MessageController extends Controller
{
- /**
- * @var string controller default action ID.
- */
- public $defaultAction = 'extract';
+ /**
+ * @var string controller default action ID.
+ */
+ public $defaultAction = 'extract';
+ /**
+ * Creates a configuration file for the "extract" command.
+ *
+ * The generated configuration file contains detailed instructions on
+ * how to customize it to fit for your needs. After customization,
+ * you may use this configuration file with the "extract" command.
+ *
+ * @param string $filePath output file name or alias.
+ * @throws Exception on failure.
+ */
+ public function actionConfig($filePath)
+ {
+ $filePath = Yii::getAlias($filePath);
+ if (file_exists($filePath)) {
+ if (!$this->confirm("File '{$filePath}' already exists. Do you wish to overwrite it?")) {
+ return;
+ }
+ }
+ copy(Yii::getAlias('@yii/views/messageConfig.php'), $filePath);
+ echo "Configuration file template created at '{$filePath}'.\n\n";
+ }
- /**
- * Creates a configuration file for the "extract" command.
- *
- * The generated configuration file contains detailed instructions on
- * how to customize it to fit for your needs. After customization,
- * you may use this configuration file with the "extract" command.
- *
- * @param string $filePath output file name or alias.
- * @throws Exception on failure.
- */
- public function actionConfig($filePath)
- {
- $filePath = Yii::getAlias($filePath);
- if (file_exists($filePath)) {
- if (!$this->confirm("File '{$filePath}' already exists. Do you wish to overwrite it?")) {
- return;
- }
- }
- copy(Yii::getAlias('@yii/views/messageConfig.php'), $filePath);
- echo "Configuration file template created at '{$filePath}'.\n\n";
- }
+ /**
+ * Extracts messages to be translated from source code.
+ *
+ * This command will search through source code files and extract
+ * messages that need to be translated in different languages.
+ *
+ * @param string $configFile the path or alias of the configuration file.
+ * You may use the "yii message/config" command to generate
+ * this file and then customize it for your needs.
+ * @throws Exception on failure.
+ */
+ public function actionExtract($configFile)
+ {
+ $configFile = Yii::getAlias($configFile);
+ if (!is_file($configFile)) {
+ throw new Exception("The configuration file does not exist: $configFile");
+ }
- /**
- * Extracts messages to be translated from source code.
- *
- * This command will search through source code files and extract
- * messages that need to be translated in different languages.
- *
- * @param string $configFile the path or alias of the configuration file.
- * You may use the "yii message/config" command to generate
- * this file and then customize it for your needs.
- * @throws Exception on failure.
- */
- public function actionExtract($configFile)
- {
- $configFile = Yii::getAlias($configFile);
- if (!is_file($configFile)) {
- throw new Exception("The configuration file does not exist: $configFile");
- }
+ $config = array_merge([
+ 'translator' => 'Yii::t',
+ 'overwrite' => false,
+ 'removeUnused' => false,
+ 'sort' => false,
+ 'format' => 'php',
+ ], require($configFile));
- $config = array_merge([
- 'translator' => 'Yii::t',
- 'overwrite' => false,
- 'removeUnused' => false,
- 'sort' => false,
- 'format' => 'php',
- ], require($configFile));
+ if (!isset($config['sourcePath'], $config['messagePath'], $config['languages'])) {
+ throw new Exception('The configuration file must specify "sourcePath", "messagePath" and "languages".');
+ }
+ if (!is_dir($config['sourcePath'])) {
+ throw new Exception("The source path {$config['sourcePath']} is not a valid directory.");
+ }
+ if (in_array($config['format'], ['php', 'po'])) {
+ if (!is_dir($config['messagePath'])) {
+ throw new Exception("The message path {$config['messagePath']} is not a valid directory.");
+ }
+ }
+ if (empty($config['languages'])) {
+ throw new Exception("Languages cannot be empty.");
+ }
+ if (empty($config['format']) || !in_array($config['format'], ['php', 'po', 'db'])) {
+ throw new Exception('Format should be either "php", "po" or "db".');
+ }
- if (!isset($config['sourcePath'], $config['messagePath'], $config['languages'])) {
- throw new Exception('The configuration file must specify "sourcePath", "messagePath" and "languages".');
- }
- if (!is_dir($config['sourcePath'])) {
- throw new Exception("The source path {$config['sourcePath']} is not a valid directory.");
- }
- if (in_array($config['format'], ['php', 'po'])) {
- if (!is_dir($config['messagePath'])) {
- throw new Exception("The message path {$config['messagePath']} is not a valid directory.");
- }
- }
- if (empty($config['languages'])) {
- throw new Exception("Languages cannot be empty.");
- }
- if (empty($config['format']) || !in_array($config['format'], ['php', 'po', 'db'])) {
- throw new Exception('Format should be either "php", "po" or "db".');
- }
+ $files = FileHelper::findFiles(realpath($config['sourcePath']), $config);
- $files = FileHelper::findFiles(realpath($config['sourcePath']), $config);
+ $messages = [];
+ foreach ($files as $file) {
+ $messages = array_merge_recursive($messages, $this->extractMessages($file, $config['translator']));
+ }
+ if (in_array($config['format'], ['php', 'po'])) {
+ foreach ($config['languages'] as $language) {
+ $dir = $config['messagePath'] . DIRECTORY_SEPARATOR . $language;
+ if (!is_dir($dir)) {
+ @mkdir($dir);
+ }
+ foreach ($messages as $category => $msgs) {
+ $file = str_replace("\\", '/', "$dir/$category." . $config['format']);
+ $path = dirname($file);
+ if (!is_dir($path)) {
+ mkdir($path, 0755, true);
+ }
+ $msgs = array_values(array_unique($msgs));
+ $this->generateMessageFile($msgs, $file, $config['overwrite'], $config['removeUnused'], $config['sort'], $config['format']);
+ }
+ }
+ } elseif ($config['format'] === 'db') {
+ $db = \Yii::$app->getComponent(isset($config['db']) ? $config['db'] : 'db');
+ if (!$db instanceof \yii\db\Connection) {
+ throw new Exception('The "db" option must refer to a valid database application component.');
+ }
+ $sourceMessageTable = isset($config['sourceMessageTable']) ? $config['sourceMessageTable'] : '{{%source_message}}';
+ $messageTable = isset($config['messageTable']) ? $config['messageTable'] : '{{%message}}';
+ $this->saveMessagesToDb(
+ $messages,
+ $db,
+ $sourceMessageTable,
+ $messageTable,
+ $config['removeUnused'],
+ $config['languages']
+ );
+ }
+ }
- $messages = [];
- foreach ($files as $file) {
- $messages = array_merge_recursive($messages, $this->extractMessages($file, $config['translator']));
- }
- if (in_array($config['format'], ['php', 'po'])) {
- foreach ($config['languages'] as $language) {
- $dir = $config['messagePath'] . DIRECTORY_SEPARATOR . $language;
- if (!is_dir($dir)) {
- @mkdir($dir);
- }
- foreach ($messages as $category => $msgs) {
- $file = str_replace("\\", '/', "$dir/$category." . $config['format']);
- $path = dirname($file);
- if (!is_dir($path)) {
- mkdir($path, 0755, true);
- }
- $msgs = array_values(array_unique($msgs));
- $this->generateMessageFile($msgs, $file, $config['overwrite'], $config['removeUnused'], $config['sort'], $config['format']);
- }
- }
- } elseif ($config['format'] === 'db') {
- $db = \Yii::$app->getComponent(isset($config['db']) ? $config['db'] : 'db');
- if (!$db instanceof \yii\db\Connection) {
- throw new Exception('The "db" option must refer to a valid database application component.');
- }
- $sourceMessageTable = isset($config['sourceMessageTable']) ? $config['sourceMessageTable'] : '{{%source_message}}';
- $messageTable = isset($config['messageTable']) ? $config['messageTable'] : '{{%message}}';
- $this->saveMessagesToDb(
- $messages,
- $db,
- $sourceMessageTable,
- $messageTable,
- $config['removeUnused'],
- $config['languages']
- );
- }
- }
+ /**
+ * Saves messages to database
+ *
+ * @param array $messages
+ * @param \yii\db\Connection $db
+ * @param string $sourceMessageTable
+ * @param string $messageTable
+ * @param boolean $removeUnused
+ * @param array $languages
+ */
+ protected function saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages)
+ {
+ $q = new \yii\db\Query;
+ $current = [];
- /**
- * Saves messages to database
- *
- * @param array $messages
- * @param \yii\db\Connection $db
- * @param string $sourceMessageTable
- * @param string $messageTable
- * @param boolean $removeUnused
- * @param array $languages
- */
- protected function saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages)
- {
- $q = new \yii\db\Query;
- $current = [];
+ foreach ($q->select(['id', 'category', 'message'])->from($sourceMessageTable)->all() as $row) {
+ $current[$row['category']][$row['id']] = $row['message'];
+ }
- foreach ($q->select(['id', 'category', 'message'])->from($sourceMessageTable)->all() as $row) {
- $current[$row['category']][$row['id']] = $row['message'];
- }
+ $new = [];
+ $obsolete = [];
- $new = [];
- $obsolete = [];
+ foreach ($messages as $category => $msgs) {
+ $msgs = array_unique($msgs);
- foreach ($messages as $category => $msgs) {
- $msgs = array_unique($msgs);
+ if (isset($current[$category])) {
+ $new[$category] = array_diff($msgs, $current[$category]);
+ $obsolete = array_diff($current[$category], $msgs);
+ } else {
+ $new[$category] = $msgs;
+ }
+ }
- if (isset($current[$category])) {
- $new[$category] = array_diff($msgs, $current[$category]);
- $obsolete = array_diff($current[$category], $msgs);
- } else {
- $new[$category] = $msgs;
- }
- }
+ foreach (array_diff(array_keys($current), array_keys($messages)) as $category) {
+ $obsolete += $current[$category];
+ }
- foreach (array_diff(array_keys($current), array_keys($messages)) as $category) {
- $obsolete += $current[$category];
- }
+ if (!$removeUnused) {
+ foreach ($obsolete as $pk => $m) {
+ if (mb_substr($m, 0, 2) === '@@' && mb_substr($m, -2) === '@@') {
+ unset($obsolete[$pk]);
+ }
+ }
+ }
- if (!$removeUnused) {
- foreach ($obsolete as $pk => $m) {
- if (mb_substr($m, 0, 2) === '@@' && mb_substr($m, -2) === '@@') {
- unset($obsolete[$pk]);
- }
- }
- }
+ $obsolete = array_keys($obsolete);
+ echo "Inserting new messages...";
+ $savedFlag = false;
- $obsolete = array_keys($obsolete);
- echo "Inserting new messages...";
- $savedFlag = false;
+ foreach ($new as $category => $msgs) {
+ foreach ($msgs as $m) {
+ $savedFlag = true;
- foreach ($new as $category => $msgs) {
- foreach ($msgs as $m) {
- $savedFlag = true;
+ $db->createCommand()
+ ->insert($sourceMessageTable, ['category' => $category, 'message' => $m])->execute();
+ $lastId = $db->getLastInsertID();
+ foreach ($languages as $language) {
+ $db->createCommand()
+ ->insert($messageTable, ['id' => $lastId, 'language' => $language])->execute();
+ }
+ }
+ }
- $db->createCommand()
- ->insert($sourceMessageTable, ['category' => $category, 'message' => $m])->execute();
- $lastId = $db->getLastInsertID();
- foreach ($languages as $language) {
- $db->createCommand()
- ->insert($messageTable, ['id' => $lastId, 'language' => $language])->execute();
- }
- }
- }
+ echo $savedFlag ? "saved.\n" : "nothing new...skipped.\n";
+ echo $removeUnused ? "Deleting obsoleted messages..." : "Updating obsoleted messages...";
- echo $savedFlag ? "saved.\n" : "nothing new...skipped.\n";
- echo $removeUnused ? "Deleting obsoleted messages..." : "Updating obsoleted messages...";
+ if (empty($obsolete)) {
+ echo "nothing obsoleted...skipped.\n";
+ } else {
+ if ($removeUnused) {
+ $db->createCommand()
+ ->delete($sourceMessageTable, ['in', 'id', $obsolete])->execute();
+ echo "deleted.\n";
+ } else {
+ $last_id = $db->getLastInsertID();
+ $db->createCommand()
+ ->update(
+ $sourceMessageTable,
+ ['message' => new \yii\db\Expression("CONCAT('@@',message,'@@')")],
+ ['in', 'id', $obsolete]
+ )->execute();
+ foreach ($languages as $language) {
+ $db->createCommand()
+ ->insert($messageTable, ['id' => $last_id, 'language' => $language])->execute();
+ }
+ echo "updated.\n";
+ }
+ }
+ }
- if (empty($obsolete)) {
- echo "nothing obsoleted...skipped.\n";
- } else {
- if ($removeUnused) {
- $db->createCommand()
- ->delete($sourceMessageTable, ['in', 'id', $obsolete])->execute();
- echo "deleted.\n";
- } else {
- $last_id = $db->getLastInsertID();
- $db->createCommand()
- ->update(
- $sourceMessageTable,
- ['message' => new \yii\db\Expression("CONCAT('@@',message,'@@')")],
- ['in', 'id', $obsolete]
- )->execute();
- foreach ($languages as $language) {
- $db->createCommand()
- ->insert($messageTable, ['id' => $last_id, 'language' => $language])->execute();
- }
- echo "updated.\n";
- }
- }
- }
+ /**
+ * Extracts messages from a file
+ *
+ * @param string $fileName name of the file to extract messages from
+ * @param string $translator name of the function used to translate messages
+ * @return array
+ */
+ protected function extractMessages($fileName, $translator)
+ {
+ echo "Extracting messages from $fileName...\n";
+ $subject = file_get_contents($fileName);
+ $messages = [];
+ if (!is_array($translator)) {
+ $translator = [$translator];
+ }
+ foreach ($translator as $currentTranslator) {
+ $n = preg_match_all(
+ '/\b' . $currentTranslator . '\s*\(\s*(\'.*?(? 0) {
- $merged[$message] = $translated[$message];
- } else {
- $untranslated[] = $message;
- }
- }
- ksort($merged);
- sort($untranslated);
- $todo = [];
- foreach ($untranslated as $message) {
- $todo[$message] = '';
- }
- ksort($translated);
- foreach ($translated as $message => $translation) {
- if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeUnused) {
- if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') {
- $todo[$message] = $translation;
- } else {
- $todo[$message] = '@@' . $translation . '@@';
- }
- }
- }
- $merged = array_merge($todo, $merged);
- if ($sort) {
- ksort($merged);
- }
- if (false === $overwrite) {
- $fileName .= '.merged';
- }
- if ($format === 'po') {
- $output = '';
- foreach ($merged as $k => $v) {
- $k = preg_replace('/(\")|(\\\")/', "\\\"", $k);
- $v = preg_replace('/(\")|(\\\")/', "\\\"", $v);
- if (substr($v, 0, 2) === '@@' && substr($v, -2) === '@@') {
- $output .= "#msgid \"$k\"\n";
- $output .= "#msgstr \"$v\"\n";
- } else {
- $output .= "msgid \"$k\"\n";
- $output .= "msgstr \"$v\"\n";
- }
- $output .= "\n";
- }
- $merged = $output;
- }
- echo "translation merged.\n";
- } else {
- if ($format === 'po') {
- $merged = '';
- sort($messages);
- foreach ($messages as $message) {
- $message = preg_replace('/(\")|(\\\")/', '\\\"', $message);
- $merged .= "msgid \"$message\"\n";
- $merged .= "msgstr \"\"\n";
- $merged .= "\n";
- }
- } else {
- $merged = [];
- foreach ($messages as $message) {
- $merged[$message] = '';
- }
- ksort($merged);
- }
- echo "saved.\n";
- }
- if ($format === 'po') {
- $content = $merged;
- } else {
- $array = str_replace("\r", '', var_export($merged, true));
- $content = << 0) {
+ $merged[$message] = $translated[$message];
+ } else {
+ $untranslated[] = $message;
+ }
+ }
+ ksort($merged);
+ sort($untranslated);
+ $todo = [];
+ foreach ($untranslated as $message) {
+ $todo[$message] = '';
+ }
+ ksort($translated);
+ foreach ($translated as $message => $translation) {
+ if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeUnused) {
+ if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') {
+ $todo[$message] = $translation;
+ } else {
+ $todo[$message] = '@@' . $translation . '@@';
+ }
+ }
+ }
+ $merged = array_merge($todo, $merged);
+ if ($sort) {
+ ksort($merged);
+ }
+ if (false === $overwrite) {
+ $fileName .= '.merged';
+ }
+ if ($format === 'po') {
+ $output = '';
+ foreach ($merged as $k => $v) {
+ $k = preg_replace('/(\")|(\\\")/', "\\\"", $k);
+ $v = preg_replace('/(\")|(\\\")/', "\\\"", $v);
+ if (substr($v, 0, 2) === '@@' && substr($v, -2) === '@@') {
+ $output .= "#msgid \"$k\"\n";
+ $output .= "#msgstr \"$v\"\n";
+ } else {
+ $output .= "msgid \"$k\"\n";
+ $output .= "msgstr \"$v\"\n";
+ }
+ $output .= "\n";
+ }
+ $merged = $output;
+ }
+ echo "translation merged.\n";
+ } else {
+ if ($format === 'po') {
+ $merged = '';
+ sort($messages);
+ foreach ($messages as $message) {
+ $message = preg_replace('/(\")|(\\\")/', '\\\"', $message);
+ $merged .= "msgid \"$message\"\n";
+ $merged .= "msgstr \"\"\n";
+ $merged .= "\n";
+ }
+ } else {
+ $merged = [];
+ foreach ($messages as $message) {
+ $merged[$message] = '';
+ }
+ ksort($merged);
+ }
+ echo "saved.\n";
+ }
+ if ($format === 'po') {
+ $content = $merged;
+ } else {
+ $array = str_replace("\r", '', var_export($merged, true));
+ $content = <<migrationPath);
- if (!is_dir($path)) {
- echo "";
- FileHelper::createDirectory($path);
- }
- $this->migrationPath = $path;
-
- if ($action->id !== 'create') {
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!$this->db instanceof Connection) {
- throw new Exception("The 'db' option must refer to the application component ID of a DB connection.");
- }
- }
-
- $version = Yii::getVersion();
- echo "Yii Migration Tool (based on Yii v{$version})\n\n";
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Upgrades the application by applying new migrations.
- * For example,
- *
- * ~~~
- * yii migrate # apply all new migrations
- * yii migrate 3 # apply the first 3 new migrations
- * ~~~
- *
- * @param integer $limit the number of new migrations to be applied. If 0, it means
- * applying all available new migrations.
- */
- public function actionUp($limit = 0)
- {
- $migrations = $this->getNewMigrations();
- if (empty($migrations)) {
- echo "No new migration found. Your system is up-to-date.\n";
- return;
- }
-
- $total = count($migrations);
- $limit = (int)$limit;
- if ($limit > 0) {
- $migrations = array_slice($migrations, 0, $limit);
- }
-
- $n = count($migrations);
- if ($n === $total) {
- echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n";
- } else {
- echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n";
- }
-
- foreach ($migrations as $migration) {
- echo " $migration\n";
- }
- echo "\n";
-
- if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
- foreach ($migrations as $migration) {
- if (!$this->migrateUp($migration)) {
- echo "\nMigration failed. The rest of the migrations are canceled.\n";
- return;
- }
- }
- echo "\nMigrated up successfully.\n";
- }
- }
-
- /**
- * Downgrades the application by reverting old migrations.
- * For example,
- *
- * ~~~
- * yii migrate/down # revert the last migration
- * yii migrate/down 3 # revert the last 3 migrations
- * ~~~
- *
- * @param integer $limit the number of migrations to be reverted. Defaults to 1,
- * meaning the last applied migration will be reverted.
- * @throws Exception if the number of the steps specified is less than 1.
- */
- public function actionDown($limit = 1)
- {
- $limit = (int)$limit;
- if ($limit < 1) {
- throw new Exception("The step argument must be greater than 0.");
- }
-
- $migrations = $this->getMigrationHistory($limit);
- if (empty($migrations)) {
- echo "No migration has been done before.\n";
- return;
- }
- $migrations = array_keys($migrations);
-
- $n = count($migrations);
- echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n";
- foreach ($migrations as $migration) {
- echo " $migration\n";
- }
- echo "\n";
-
- if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
- foreach ($migrations as $migration) {
- if (!$this->migrateDown($migration)) {
- echo "\nMigration failed. The rest of the migrations are canceled.\n";
- return;
- }
- }
- echo "\nMigrated down successfully.\n";
- }
- }
-
- /**
- * Redoes the last few migrations.
- *
- * This command will first revert the specified migrations, and then apply
- * them again. For example,
- *
- * ~~~
- * yii migrate/redo # redo the last applied migration
- * yii migrate/redo 3 # redo the last 3 applied migrations
- * ~~~
- *
- * @param integer $limit the number of migrations to be redone. Defaults to 1,
- * meaning the last applied migration will be redone.
- * @throws Exception if the number of the steps specified is less than 1.
- */
- public function actionRedo($limit = 1)
- {
- $limit = (int)$limit;
- if ($limit < 1) {
- throw new Exception("The step argument must be greater than 0.");
- }
-
- $migrations = $this->getMigrationHistory($limit);
- if (empty($migrations)) {
- echo "No migration has been done before.\n";
- return;
- }
- $migrations = array_keys($migrations);
-
- $n = count($migrations);
- echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n";
- foreach ($migrations as $migration) {
- echo " $migration\n";
- }
- echo "\n";
-
- if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
- foreach ($migrations as $migration) {
- if (!$this->migrateDown($migration)) {
- echo "\nMigration failed. The rest of the migrations are canceled.\n";
- return;
- }
- }
- foreach (array_reverse($migrations) as $migration) {
- if (!$this->migrateUp($migration)) {
- echo "\nMigration failed. The rest of the migrations migrations are canceled.\n";
- return;
- }
- }
- echo "\nMigration redone successfully.\n";
- }
- }
-
- /**
- * Upgrades or downgrades till the specified version.
- *
- * Can also downgrade versions to the certain apply time in the past by providing
- * a UNIX timestamp or a string parseable by the strtotime() function. This means
- * that all the versions applied after the specified certain time would be reverted.
- *
- * This command will first revert the specified migrations, and then apply
- * them again. For example,
- *
- * ~~~
- * yii migrate/to 101129_185401 # using timestamp
- * yii migrate/to m101129_185401_create_user_table # using full name
- * yii migrate/to 1392853618 # using UNIX timestamp
- * yii migrate/to "2014-02-15 13:00:50" # using strtotime() parseable string
- * ~~~
- *
- * @param string $version either the version name or the certain time value in the past
- * that the application should be migrated to. This can be either the timestamp,
- * the full name of the migration, the UNIX timestamp, or the parseable datetime
- * string.
- * @throws Exception if the version argument is invalid.
- */
- public function actionTo($version)
- {
- if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
- $this->migrateToVersion('m' . $matches[1]);
- } elseif ((string)(int)$version == $version) {
- $this->migrateToTime($version);
- } elseif (($time = strtotime($version)) !== false) {
- $this->migrateToTime($time);
- } else {
- throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401),\n the full name of a migration (e.g. m101129_185401_create_user_table),\n a UNIX timestamp (e.g. 1392853000), or a datetime string parseable\nby the strtotime() function (e.g. 2014-02-15 13:00:50).");
- }
- }
-
- /**
- * Modifies the migration history to the specified version.
- *
- * No actual migration will be performed.
- *
- * ~~~
- * yii migrate/mark 101129_185401 # using timestamp
- * yii migrate/mark m101129_185401_create_user_table # using full name
- * ~~~
- *
- * @param string $version the version at which the migration history should be marked.
- * This can be either the timestamp or the full name of the migration.
- * @throws Exception if the version argument is invalid or the version cannot be found.
- */
- public function actionMark($version)
- {
- $originalVersion = $version;
- if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
- $version = 'm' . $matches[1];
- } else {
- throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
- }
-
- // try mark up
- $migrations = $this->getNewMigrations();
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- if ($this->confirm("Set migration history at $originalVersion?")) {
- $command = $this->db->createCommand();
- for ($j = 0; $j <= $i; ++$j) {
- $command->insert($this->migrationTable, [
- 'version' => $migrations[$j],
- 'apply_time' => time(),
- ])->execute();
- }
- echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
- }
- return;
- }
- }
-
- // try mark down
- $migrations = array_keys($this->getMigrationHistory(-1));
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- if ($i === 0) {
- echo "Already at '$originalVersion'. Nothing needs to be done.\n";
- } else {
- if ($this->confirm("Set migration history at $originalVersion?")) {
- $command = $this->db->createCommand();
- for ($j = 0; $j < $i; ++$j) {
- $command->delete($this->migrationTable, [
- 'version' => $migrations[$j],
- ])->execute();
- }
- echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
- }
- }
- return;
- }
- }
-
- throw new Exception("Unable to find the version '$originalVersion'.");
- }
-
- /**
- * Displays the migration history.
- *
- * This command will show the list of migrations that have been applied
- * so far. For example,
- *
- * ~~~
- * yii migrate/history # showing the last 10 migrations
- * yii migrate/history 5 # showing the last 5 migrations
- * yii migrate/history 0 # showing the whole history
- * ~~~
- *
- * @param integer $limit the maximum number of migrations to be displayed.
- * If it is 0, the whole migration history will be displayed.
- */
- public function actionHistory($limit = 10)
- {
- $limit = (int)$limit;
- $migrations = $this->getMigrationHistory($limit);
- if (empty($migrations)) {
- echo "No migration has been done before.\n";
- } else {
- $n = count($migrations);
- if ($limit > 0) {
- echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
- } else {
- echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n";
- }
- foreach ($migrations as $version => $time) {
- echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n";
- }
- }
- }
-
- /**
- * Displays the un-applied new migrations.
- *
- * This command will show the new migrations that have not been applied.
- * For example,
- *
- * ~~~
- * yii migrate/new # showing the first 10 new migrations
- * yii migrate/new 5 # showing the first 5 new migrations
- * yii migrate/new 0 # showing all new migrations
- * ~~~
- *
- * @param integer $limit the maximum number of new migrations to be displayed.
- * If it is 0, all available new migrations will be displayed.
- */
- public function actionNew($limit = 10)
- {
- $limit = (int)$limit;
- $migrations = $this->getNewMigrations();
- if (empty($migrations)) {
- echo "No new migrations found. Your system is up-to-date.\n";
- } else {
- $n = count($migrations);
- if ($limit > 0 && $n > $limit) {
- $migrations = array_slice($migrations, 0, $limit);
- echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
- } else {
- echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
- }
-
- foreach ($migrations as $migration) {
- echo " " . $migration . "\n";
- }
- }
- }
-
- /**
- * Creates a new migration.
- *
- * This command creates a new migration using the available migration template.
- * After using this command, developers should modify the created migration
- * skeleton by filling up the actual migration logic.
- *
- * ~~~
- * yii migrate/create create_user_table
- * ~~~
- *
- * @param string $name the name of the new migration. This should only contain
- * letters, digits and/or underscores.
- * @throws Exception if the name argument is invalid.
- */
- public function actionCreate($name)
- {
- if (!preg_match('/^\w+$/', $name)) {
- throw new Exception("The migration name should contain letters, digits and/or underscore characters only.");
- }
-
- $name = 'm' . gmdate('ymd_His') . '_' . $name;
- $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php';
-
- if ($this->confirm("Create new migration '$file'?")) {
- $content = $this->renderFile(Yii::getAlias($this->templateFile), ['className' => $name]);
- file_put_contents($file, $content);
- echo "New migration created successfully.\n";
- }
- }
-
- /**
- * Upgrades with the specified migration class.
- * @param string $class the migration class name
- * @return boolean whether the migration is successful
- */
- protected function migrateUp($class)
- {
- if ($class === self::BASE_MIGRATION) {
- return true;
- }
-
- echo "*** applying $class\n";
- $start = microtime(true);
- $migration = $this->createMigration($class);
- if ($migration->up() !== false) {
- $this->db->createCommand()->insert($this->migrationTable, [
- 'version' => $class,
- 'apply_time' => time(),
- ])->execute();
- $time = microtime(true) - $start;
- echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return true;
- } else {
- $time = microtime(true) - $start;
- echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return false;
- }
- }
-
- /**
- * Downgrades with the specified migration class.
- * @param string $class the migration class name
- * @return boolean whether the migration is successful
- */
- protected function migrateDown($class)
- {
- if ($class === self::BASE_MIGRATION) {
- return true;
- }
-
- echo "*** reverting $class\n";
- $start = microtime(true);
- $migration = $this->createMigration($class);
- if ($migration->down() !== false) {
- $this->db->createCommand()->delete($this->migrationTable, [
- 'version' => $class,
- ])->execute();
- $time = microtime(true) - $start;
- echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return true;
- } else {
- $time = microtime(true) - $start;
- echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return false;
- }
- }
-
- /**
- * Creates a new migration instance.
- * @param string $class the migration class name
- * @return \yii\db\Migration the migration instance
- */
- protected function createMigration($class)
- {
- $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
- require_once($file);
- return new $class(['db' => $this->db]);
- }
-
- /**
- * Migrates to the specified apply time in the past.
- * @param integer $time UNIX timestamp value.
- */
- protected function migrateToTime($time)
- {
- $count = 0;
- $migrations = array_values($this->getMigrationHistory(-1));
- while ($count < count($migrations) && $migrations[$count] > $time) {
- ++$count;
- }
- if ($count === 0) {
- echo "Nothing needs to be done.\n";
- } else {
- $this->actionDown($count);
- }
- }
-
- /**
- * Migrates to the certain version.
- * @param string $version name in the full format.
- * @throws Exception if the provided version cannot be found.
- */
- protected function migrateToVersion($version)
- {
- $originalVersion = $version;
-
- // try migrate up
- $migrations = $this->getNewMigrations();
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- $this->actionUp($i + 1);
- return;
- }
- }
-
- // try migrate down
- $migrations = array_keys($this->getMigrationHistory(-1));
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- if ($i === 0) {
- echo "Already at '$originalVersion'. Nothing needs to be done.\n";
- } else {
- $this->actionDown($i);
- }
- return;
- }
- }
-
- throw new Exception("Unable to find the version '$originalVersion'.");
- }
-
- /**
- * Returns the migration history.
- * @param integer $limit the maximum number of records in the history to be returned
- * @return array the migration history
- */
- protected function getMigrationHistory($limit)
- {
- if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
- $this->createMigrationHistoryTable();
- }
- $query = new Query;
- $rows = $query->select(['version', 'apply_time'])
- ->from($this->migrationTable)
- ->orderBy('version DESC')
- ->limit($limit)
- ->createCommand($this->db)
- ->queryAll();
- $history = ArrayHelper::map($rows, 'version', 'apply_time');
- unset($history[self::BASE_MIGRATION]);
- return $history;
- }
-
- /**
- * Creates the migration history table.
- */
- protected function createMigrationHistoryTable()
- {
- $tableName = $this->db->schema->getRawTableName($this->migrationTable);
- echo "Creating migration history table \"$tableName\"...";
- $this->db->createCommand()->createTable($this->migrationTable, [
- 'version' => 'varchar(180) NOT NULL PRIMARY KEY',
- 'apply_time' => 'integer',
- ])->execute();
- $this->db->createCommand()->insert($this->migrationTable, [
- 'version' => self::BASE_MIGRATION,
- 'apply_time' => time(),
- ])->execute();
- echo "done.\n";
- }
-
- /**
- * Returns the migrations that are not applied.
- * @return array list of new migrations
- */
- protected function getNewMigrations()
- {
- $applied = [];
- foreach ($this->getMigrationHistory(-1) as $version => $time) {
- $applied[substr($version, 1, 13)] = true;
- }
-
- $migrations = [];
- $handle = opendir($this->migrationPath);
- while (($file = readdir($handle)) !== false) {
- if ($file === '.' || $file === '..') {
- continue;
- }
- $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
- if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
- $migrations[] = $matches[1];
- }
- }
- closedir($handle);
- sort($migrations);
- return $migrations;
- }
+ /**
+ * The name of the dummy migration that marks the beginning of the whole migration history.
+ */
+ const BASE_MIGRATION = 'm000000_000000_base';
+
+ /**
+ * @var string the default command action.
+ */
+ public $defaultAction = 'up';
+ /**
+ * @var string the directory storing the migration classes. This can be either
+ * a path alias or a directory.
+ */
+ public $migrationPath = '@app/migrations';
+ /**
+ * @var string the name of the table for keeping applied migration information.
+ */
+ public $migrationTable = '{{%migration}}';
+ /**
+ * @var string the template file for generating new migrations.
+ * This can be either a path alias (e.g. "@app/migrations/template.php")
+ * or a file path.
+ */
+ public $templateFile = '@yii/views/migration.php';
+ /**
+ * @var boolean whether to execute the migration in an interactive mode.
+ */
+ public $interactive = true;
+ /**
+ * @var Connection|string the DB connection object or the application
+ * component ID of the DB connection.
+ */
+ public $db = 'db';
+
+ /**
+ * Returns the names of the global options for this command.
+ * @return array the names of the global options for this command.
+ */
+ public function options($id)
+ {
+ return array_merge(parent::options($id),
+ ['migrationPath', 'migrationTable', 'db'], // global for all actions
+ ($id == 'create') ? ['templateFile'] : [] // action create
+ );
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * It checks the existence of the [[migrationPath]].
+ * @param \yii\base\Action $action the action to be executed.
+ * @throws Exception if db component isn't configured
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ if (parent::beforeAction($action)) {
+ $path = Yii::getAlias($this->migrationPath);
+ if (!is_dir($path)) {
+ echo "";
+ FileHelper::createDirectory($path);
+ }
+ $this->migrationPath = $path;
+
+ if ($action->id !== 'create') {
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new Exception("The 'db' option must refer to the application component ID of a DB connection.");
+ }
+ }
+
+ $version = Yii::getVersion();
+ echo "Yii Migration Tool (based on Yii v{$version})\n\n";
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Upgrades the application by applying new migrations.
+ * For example,
+ *
+ * ~~~
+ * yii migrate # apply all new migrations
+ * yii migrate 3 # apply the first 3 new migrations
+ * ~~~
+ *
+ * @param integer $limit the number of new migrations to be applied. If 0, it means
+ * applying all available new migrations.
+ */
+ public function actionUp($limit = 0)
+ {
+ $migrations = $this->getNewMigrations();
+ if (empty($migrations)) {
+ echo "No new migration found. Your system is up-to-date.\n";
+
+ return;
+ }
+
+ $total = count($migrations);
+ $limit = (int) $limit;
+ if ($limit > 0) {
+ $migrations = array_slice($migrations, 0, $limit);
+ }
+
+ $n = count($migrations);
+ if ($n === $total) {
+ echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+ } else {
+ echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+ }
+
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateUp($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+
+ return;
+ }
+ }
+ echo "\nMigrated up successfully.\n";
+ }
+ }
+
+ /**
+ * Downgrades the application by reverting old migrations.
+ * For example,
+ *
+ * ~~~
+ * yii migrate/down # revert the last migration
+ * yii migrate/down 3 # revert the last 3 migrations
+ * ~~~
+ *
+ * @param integer $limit the number of migrations to be reverted. Defaults to 1,
+ * meaning the last applied migration will be reverted.
+ * @throws Exception if the number of the steps specified is less than 1.
+ */
+ public function actionDown($limit = 1)
+ {
+ $limit = (int) $limit;
+ if ($limit < 1) {
+ throw new Exception("The step argument must be greater than 0.");
+ }
+
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+
+ return;
+ }
+ $migrations = array_keys($migrations);
+
+ $n = count($migrations);
+ echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n";
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateDown($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+
+ return;
+ }
+ }
+ echo "\nMigrated down successfully.\n";
+ }
+ }
+
+ /**
+ * Redoes the last few migrations.
+ *
+ * This command will first revert the specified migrations, and then apply
+ * them again. For example,
+ *
+ * ~~~
+ * yii migrate/redo # redo the last applied migration
+ * yii migrate/redo 3 # redo the last 3 applied migrations
+ * ~~~
+ *
+ * @param integer $limit the number of migrations to be redone. Defaults to 1,
+ * meaning the last applied migration will be redone.
+ * @throws Exception if the number of the steps specified is less than 1.
+ */
+ public function actionRedo($limit = 1)
+ {
+ $limit = (int) $limit;
+ if ($limit < 1) {
+ throw new Exception("The step argument must be greater than 0.");
+ }
+
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+
+ return;
+ }
+ $migrations = array_keys($migrations);
+
+ $n = count($migrations);
+ echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n";
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateDown($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+
+ return;
+ }
+ }
+ foreach (array_reverse($migrations) as $migration) {
+ if (!$this->migrateUp($migration)) {
+ echo "\nMigration failed. The rest of the migrations migrations are canceled.\n";
+
+ return;
+ }
+ }
+ echo "\nMigration redone successfully.\n";
+ }
+ }
+
+ /**
+ * Upgrades or downgrades till the specified version.
+ *
+ * Can also downgrade versions to the certain apply time in the past by providing
+ * a UNIX timestamp or a string parseable by the strtotime() function. This means
+ * that all the versions applied after the specified certain time would be reverted.
+ *
+ * This command will first revert the specified migrations, and then apply
+ * them again. For example,
+ *
+ * ~~~
+ * yii migrate/to 101129_185401 # using timestamp
+ * yii migrate/to m101129_185401_create_user_table # using full name
+ * yii migrate/to 1392853618 # using UNIX timestamp
+ * yii migrate/to "2014-02-15 13:00:50" # using strtotime() parseable string
+ * ~~~
+ *
+ * @param string $version either the version name or the certain time value in the past
+ * that the application should be migrated to. This can be either the timestamp,
+ * the full name of the migration, the UNIX timestamp, or the parseable datetime
+ * string.
+ * @throws Exception if the version argument is invalid.
+ */
+ public function actionTo($version)
+ {
+ if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+ $this->migrateToVersion('m' . $matches[1]);
+ } elseif ((string) (int) $version == $version) {
+ $this->migrateToTime($version);
+ } elseif (($time = strtotime($version)) !== false) {
+ $this->migrateToTime($time);
+ } else {
+ throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401),\n the full name of a migration (e.g. m101129_185401_create_user_table),\n a UNIX timestamp (e.g. 1392853000), or a datetime string parseable\nby the strtotime() function (e.g. 2014-02-15 13:00:50).");
+ }
+ }
+
+ /**
+ * Modifies the migration history to the specified version.
+ *
+ * No actual migration will be performed.
+ *
+ * ~~~
+ * yii migrate/mark 101129_185401 # using timestamp
+ * yii migrate/mark m101129_185401_create_user_table # using full name
+ * ~~~
+ *
+ * @param string $version the version at which the migration history should be marked.
+ * This can be either the timestamp or the full name of the migration.
+ * @throws Exception if the version argument is invalid or the version cannot be found.
+ */
+ public function actionMark($version)
+ {
+ $originalVersion = $version;
+ if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+ $version = 'm' . $matches[1];
+ } else {
+ throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
+ }
+
+ // try mark up
+ $migrations = $this->getNewMigrations();
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($this->confirm("Set migration history at $originalVersion?")) {
+ $command = $this->db->createCommand();
+ for ($j = 0; $j <= $i; ++$j) {
+ $command->insert($this->migrationTable, [
+ 'version' => $migrations[$j],
+ 'apply_time' => time(),
+ ])->execute();
+ }
+ echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+ }
+
+ return;
+ }
+ }
+
+ // try mark down
+ $migrations = array_keys($this->getMigrationHistory(-1));
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($i === 0) {
+ echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+ } else {
+ if ($this->confirm("Set migration history at $originalVersion?")) {
+ $command = $this->db->createCommand();
+ for ($j = 0; $j < $i; ++$j) {
+ $command->delete($this->migrationTable, [
+ 'version' => $migrations[$j],
+ ])->execute();
+ }
+ echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+ }
+ }
+
+ return;
+ }
+ }
+
+ throw new Exception("Unable to find the version '$originalVersion'.");
+ }
+
+ /**
+ * Displays the migration history.
+ *
+ * This command will show the list of migrations that have been applied
+ * so far. For example,
+ *
+ * ~~~
+ * yii migrate/history # showing the last 10 migrations
+ * yii migrate/history 5 # showing the last 5 migrations
+ * yii migrate/history 0 # showing the whole history
+ * ~~~
+ *
+ * @param integer $limit the maximum number of migrations to be displayed.
+ * If it is 0, the whole migration history will be displayed.
+ */
+ public function actionHistory($limit = 10)
+ {
+ $limit = (int) $limit;
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+ } else {
+ $n = count($migrations);
+ if ($limit > 0) {
+ echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ } else {
+ echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n";
+ }
+ foreach ($migrations as $version => $time) {
+ echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n";
+ }
+ }
+ }
+
+ /**
+ * Displays the un-applied new migrations.
+ *
+ * This command will show the new migrations that have not been applied.
+ * For example,
+ *
+ * ~~~
+ * yii migrate/new # showing the first 10 new migrations
+ * yii migrate/new 5 # showing the first 5 new migrations
+ * yii migrate/new 0 # showing all new migrations
+ * ~~~
+ *
+ * @param integer $limit the maximum number of new migrations to be displayed.
+ * If it is 0, all available new migrations will be displayed.
+ */
+ public function actionNew($limit = 10)
+ {
+ $limit = (int) $limit;
+ $migrations = $this->getNewMigrations();
+ if (empty($migrations)) {
+ echo "No new migrations found. Your system is up-to-date.\n";
+ } else {
+ $n = count($migrations);
+ if ($limit > 0 && $n > $limit) {
+ $migrations = array_slice($migrations, 0, $limit);
+ echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ } else {
+ echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ }
+
+ foreach ($migrations as $migration) {
+ echo " " . $migration . "\n";
+ }
+ }
+ }
+
+ /**
+ * Creates a new migration.
+ *
+ * This command creates a new migration using the available migration template.
+ * After using this command, developers should modify the created migration
+ * skeleton by filling up the actual migration logic.
+ *
+ * ~~~
+ * yii migrate/create create_user_table
+ * ~~~
+ *
+ * @param string $name the name of the new migration. This should only contain
+ * letters, digits and/or underscores.
+ * @throws Exception if the name argument is invalid.
+ */
+ public function actionCreate($name)
+ {
+ if (!preg_match('/^\w+$/', $name)) {
+ throw new Exception("The migration name should contain letters, digits and/or underscore characters only.");
+ }
+
+ $name = 'm' . gmdate('ymd_His') . '_' . $name;
+ $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php';
+
+ if ($this->confirm("Create new migration '$file'?")) {
+ $content = $this->renderFile(Yii::getAlias($this->templateFile), ['className' => $name]);
+ file_put_contents($file, $content);
+ echo "New migration created successfully.\n";
+ }
+ }
+
+ /**
+ * Upgrades with the specified migration class.
+ * @param string $class the migration class name
+ * @return boolean whether the migration is successful
+ */
+ protected function migrateUp($class)
+ {
+ if ($class === self::BASE_MIGRATION) {
+ return true;
+ }
+
+ echo "*** applying $class\n";
+ $start = microtime(true);
+ $migration = $this->createMigration($class);
+ if ($migration->up() !== false) {
+ $this->db->createCommand()->insert($this->migrationTable, [
+ 'version' => $class,
+ 'apply_time' => time(),
+ ])->execute();
+ $time = microtime(true) - $start;
+ echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+
+ return true;
+ } else {
+ $time = microtime(true) - $start;
+ echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+
+ return false;
+ }
+ }
+
+ /**
+ * Downgrades with the specified migration class.
+ * @param string $class the migration class name
+ * @return boolean whether the migration is successful
+ */
+ protected function migrateDown($class)
+ {
+ if ($class === self::BASE_MIGRATION) {
+ return true;
+ }
+
+ echo "*** reverting $class\n";
+ $start = microtime(true);
+ $migration = $this->createMigration($class);
+ if ($migration->down() !== false) {
+ $this->db->createCommand()->delete($this->migrationTable, [
+ 'version' => $class,
+ ])->execute();
+ $time = microtime(true) - $start;
+ echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+
+ return true;
+ } else {
+ $time = microtime(true) - $start;
+ echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+
+ return false;
+ }
+ }
+
+ /**
+ * Creates a new migration instance.
+ * @param string $class the migration class name
+ * @return \yii\db\Migration the migration instance
+ */
+ protected function createMigration($class)
+ {
+ $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
+ require_once($file);
+
+ return new $class(['db' => $this->db]);
+ }
+
+ /**
+ * Migrates to the specified apply time in the past.
+ * @param integer $time UNIX timestamp value.
+ */
+ protected function migrateToTime($time)
+ {
+ $count = 0;
+ $migrations = array_values($this->getMigrationHistory(-1));
+ while ($count < count($migrations) && $migrations[$count] > $time) {
+ ++$count;
+ }
+ if ($count === 0) {
+ echo "Nothing needs to be done.\n";
+ } else {
+ $this->actionDown($count);
+ }
+ }
+
+ /**
+ * Migrates to the certain version.
+ * @param string $version name in the full format.
+ * @throws Exception if the provided version cannot be found.
+ */
+ protected function migrateToVersion($version)
+ {
+ $originalVersion = $version;
+
+ // try migrate up
+ $migrations = $this->getNewMigrations();
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ $this->actionUp($i + 1);
+
+ return;
+ }
+ }
+
+ // try migrate down
+ $migrations = array_keys($this->getMigrationHistory(-1));
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($i === 0) {
+ echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+ } else {
+ $this->actionDown($i);
+ }
+
+ return;
+ }
+ }
+
+ throw new Exception("Unable to find the version '$originalVersion'.");
+ }
+
+ /**
+ * Returns the migration history.
+ * @param integer $limit the maximum number of records in the history to be returned
+ * @return array the migration history
+ */
+ protected function getMigrationHistory($limit)
+ {
+ if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
+ $this->createMigrationHistoryTable();
+ }
+ $query = new Query;
+ $rows = $query->select(['version', 'apply_time'])
+ ->from($this->migrationTable)
+ ->orderBy('version DESC')
+ ->limit($limit)
+ ->createCommand($this->db)
+ ->queryAll();
+ $history = ArrayHelper::map($rows, 'version', 'apply_time');
+ unset($history[self::BASE_MIGRATION]);
+
+ return $history;
+ }
+
+ /**
+ * Creates the migration history table.
+ */
+ protected function createMigrationHistoryTable()
+ {
+ $tableName = $this->db->schema->getRawTableName($this->migrationTable);
+ echo "Creating migration history table \"$tableName\"...";
+ $this->db->createCommand()->createTable($this->migrationTable, [
+ 'version' => 'varchar(180) NOT NULL PRIMARY KEY',
+ 'apply_time' => 'integer',
+ ])->execute();
+ $this->db->createCommand()->insert($this->migrationTable, [
+ 'version' => self::BASE_MIGRATION,
+ 'apply_time' => time(),
+ ])->execute();
+ echo "done.\n";
+ }
+
+ /**
+ * Returns the migrations that are not applied.
+ * @return array list of new migrations
+ */
+ protected function getNewMigrations()
+ {
+ $applied = [];
+ foreach ($this->getMigrationHistory(-1) as $version => $time) {
+ $applied[substr($version, 1, 13)] = true;
+ }
+
+ $migrations = [];
+ $handle = opendir($this->migrationPath);
+ while (($file = readdir($handle)) !== false) {
+ if ($file === '.' || $file === '..') {
+ continue;
+ }
+ $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
+ if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
+ $migrations[] = $matches[1];
+ }
+ }
+ closedir($handle);
+ sort($migrations);
+
+ return $migrations;
+ }
}
diff --git a/framework/data/ActiveDataProvider.php b/framework/data/ActiveDataProvider.php
index 6618153f8ce..073eb1b500c 100644
--- a/framework/data/ActiveDataProvider.php
+++ b/framework/data/ActiveDataProvider.php
@@ -53,130 +53,134 @@
*/
class ActiveDataProvider extends BaseDataProvider
{
- /**
- * @var QueryInterface the query that is used to fetch data models and [[totalCount]]
- * if it is not explicitly set.
- */
- public $query;
- /**
- * @var string|callable the column that is used as the key of the data models.
- * This can be either a column name, or a callable that returns the key value of a given data model.
- *
- * If this is not set, the following rules will be used to determine the keys of the data models:
- *
- * - If [[query]] is an [[\yii\db\ActiveQuery]] instance, the primary keys of [[\yii\db\ActiveQuery::modelClass]] will be used.
- * - Otherwise, the keys of the [[models]] array will be used.
- *
- * @see getKeys()
- */
- public $key;
- /**
- * @var Connection|string the DB connection object or the application component ID of the DB connection.
- * If not set, the default DB connection will be used.
- */
- public $db;
+ /**
+ * @var QueryInterface the query that is used to fetch data models and [[totalCount]]
+ * if it is not explicitly set.
+ */
+ public $query;
+ /**
+ * @var string|callable the column that is used as the key of the data models.
+ * This can be either a column name, or a callable that returns the key value of a given data model.
+ *
+ * If this is not set, the following rules will be used to determine the keys of the data models:
+ *
+ * - If [[query]] is an [[\yii\db\ActiveQuery]] instance, the primary keys of [[\yii\db\ActiveQuery::modelClass]] will be used.
+ * - Otherwise, the keys of the [[models]] array will be used.
+ *
+ * @see getKeys()
+ */
+ public $key;
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ * If not set, the default DB connection will be used.
+ */
+ public $db;
- /**
- * Initializes the DB connection component.
- * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
- * @throws InvalidConfigException if [[db]] is invalid.
- */
- public function init()
- {
- parent::init();
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- if ($this->db === null) {
- throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.');
- }
- }
- }
+ /**
+ * Initializes the DB connection component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ if ($this->db === null) {
+ throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.');
+ }
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function prepareModels()
- {
- if (!$this->query instanceof QueryInterface) {
- throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
- }
- if (($pagination = $this->getPagination()) !== false) {
- $pagination->totalCount = $this->getTotalCount();
- $this->query->limit($pagination->getLimit())->offset($pagination->getOffset());
- }
- if (($sort = $this->getSort()) !== false) {
- $this->query->addOrderBy($sort->getOrders());
- }
- return $this->query->all($this->db);
- }
+ /**
+ * @inheritdoc
+ */
+ protected function prepareModels()
+ {
+ if (!$this->query instanceof QueryInterface) {
+ throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
+ }
+ if (($pagination = $this->getPagination()) !== false) {
+ $pagination->totalCount = $this->getTotalCount();
+ $this->query->limit($pagination->getLimit())->offset($pagination->getOffset());
+ }
+ if (($sort = $this->getSort()) !== false) {
+ $this->query->addOrderBy($sort->getOrders());
+ }
- /**
- * @inheritdoc
- */
- protected function prepareKeys($models)
- {
- $keys = [];
- if ($this->key !== null) {
- foreach ($models as $model) {
- if (is_string($this->key)) {
- $keys[] = $model[$this->key];
- } else {
- $keys[] = call_user_func($this->key, $model);
- }
- }
- return $keys;
- } elseif ($this->query instanceof ActiveQueryInterface) {
- /** @var \yii\db\ActiveRecord $class */
- $class = $this->query->modelClass;
- $pks = $class::primaryKey();
- if (count($pks) === 1) {
- $pk = $pks[0];
- foreach ($models as $model) {
- $keys[] = $model[$pk];
- }
- } else {
- foreach ($models as $model) {
- $kk = [];
- foreach ($pks as $pk) {
- $kk[$pk] = $model[$pk];
- }
- $keys[] = $kk;
- }
- }
- return $keys;
- } else {
- return array_keys($models);
- }
- }
+ return $this->query->all($this->db);
+ }
- /**
- * @inheritdoc
- */
- protected function prepareTotalCount()
- {
- if (!$this->query instanceof QueryInterface) {
- throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
- }
- $query = clone $this->query;
- return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db);
- }
+ /**
+ * @inheritdoc
+ */
+ protected function prepareKeys($models)
+ {
+ $keys = [];
+ if ($this->key !== null) {
+ foreach ($models as $model) {
+ if (is_string($this->key)) {
+ $keys[] = $model[$this->key];
+ } else {
+ $keys[] = call_user_func($this->key, $model);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function setSort($value)
- {
- parent::setSort($value);
- if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQueryInterface) {
- /** @var Model $model */
- $model = new $this->query->modelClass;
- foreach ($model->attributes() as $attribute) {
- $sort->attributes[$attribute] = [
- 'asc' => [$attribute => SORT_ASC],
- 'desc' => [$attribute => SORT_DESC],
- 'label' => $model->getAttributeLabel($attribute),
- ];
- }
- }
- }
+ return $keys;
+ } elseif ($this->query instanceof ActiveQueryInterface) {
+ /** @var \yii\db\ActiveRecord $class */
+ $class = $this->query->modelClass;
+ $pks = $class::primaryKey();
+ if (count($pks) === 1) {
+ $pk = $pks[0];
+ foreach ($models as $model) {
+ $keys[] = $model[$pk];
+ }
+ } else {
+ foreach ($models as $model) {
+ $kk = [];
+ foreach ($pks as $pk) {
+ $kk[$pk] = $model[$pk];
+ }
+ $keys[] = $kk;
+ }
+ }
+
+ return $keys;
+ } else {
+ return array_keys($models);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareTotalCount()
+ {
+ if (!$this->query instanceof QueryInterface) {
+ throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
+ }
+ $query = clone $this->query;
+
+ return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setSort($value)
+ {
+ parent::setSort($value);
+ if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQueryInterface) {
+ /** @var Model $model */
+ $model = new $this->query->modelClass;
+ foreach ($model->attributes() as $attribute) {
+ $sort->attributes[$attribute] = [
+ 'asc' => [$attribute => SORT_ASC],
+ 'desc' => [$attribute => SORT_DESC],
+ 'label' => $model->getAttributeLabel($attribute),
+ ];
+ }
+ }
+ }
}
diff --git a/framework/data/ArrayDataProvider.php b/framework/data/ArrayDataProvider.php
index 2b694c7c060..4ec2d117046 100644
--- a/framework/data/ArrayDataProvider.php
+++ b/framework/data/ArrayDataProvider.php
@@ -50,82 +50,83 @@
*/
class ArrayDataProvider extends BaseDataProvider
{
- /**
- * @var string|callable the column that is used as the key of the data models.
- * This can be either a column name, or a callable that returns the key value of a given data model.
- * If this is not set, the index of the [[models]] array will be used.
- * @see getKeys()
- */
- public $key;
- /**
- * @var array the data that is not paginated or sorted. When pagination is enabled,
- * this property usually contains more elements than [[models]].
- * The array elements must use zero-based integer keys.
- */
- public $allModels;
+ /**
+ * @var string|callable the column that is used as the key of the data models.
+ * This can be either a column name, or a callable that returns the key value of a given data model.
+ * If this is not set, the index of the [[models]] array will be used.
+ * @see getKeys()
+ */
+ public $key;
+ /**
+ * @var array the data that is not paginated or sorted. When pagination is enabled,
+ * this property usually contains more elements than [[models]].
+ * The array elements must use zero-based integer keys.
+ */
+ public $allModels;
+ /**
+ * @inheritdoc
+ */
+ protected function prepareModels()
+ {
+ if (($models = $this->allModels) === null) {
+ return [];
+ }
- /**
- * @inheritdoc
- */
- protected function prepareModels()
- {
- if (($models = $this->allModels) === null) {
- return [];
- }
+ if (($sort = $this->getSort()) !== false) {
+ $models = $this->sortModels($models, $sort);
+ }
- if (($sort = $this->getSort()) !== false) {
- $models = $this->sortModels($models, $sort);
- }
+ if (($pagination = $this->getPagination()) !== false) {
+ $pagination->totalCount = $this->getTotalCount();
+ $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit());
+ }
- if (($pagination = $this->getPagination()) !== false) {
- $pagination->totalCount = $this->getTotalCount();
- $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit());
- }
+ return $models;
+ }
- return $models;
- }
+ /**
+ * @inheritdoc
+ */
+ protected function prepareKeys($models)
+ {
+ if ($this->key !== null) {
+ $keys = [];
+ foreach ($models as $model) {
+ if (is_string($this->key)) {
+ $keys[] = $model[$this->key];
+ } else {
+ $keys[] = call_user_func($this->key, $model);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function prepareKeys($models)
- {
- if ($this->key !== null) {
- $keys = [];
- foreach ($models as $model) {
- if (is_string($this->key)) {
- $keys[] = $model[$this->key];
- } else {
- $keys[] = call_user_func($this->key, $model);
- }
- }
- return $keys;
- } else {
- return array_keys($models);
- }
- }
+ return $keys;
+ } else {
+ return array_keys($models);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function prepareTotalCount()
- {
- return count($this->allModels);
- }
+ /**
+ * @inheritdoc
+ */
+ protected function prepareTotalCount()
+ {
+ return count($this->allModels);
+ }
- /**
- * Sorts the data models according to the given sort definition
- * @param array $models the models to be sorted
- * @param Sort $sort the sort definition
- * @return array the sorted data models
- */
- protected function sortModels($models, $sort)
- {
- $orders = $sort->getOrders();
- if (!empty($orders)) {
- ArrayHelper::multisort($models, array_keys($orders), array_values($orders));
- }
- return $models;
- }
+ /**
+ * Sorts the data models according to the given sort definition
+ * @param array $models the models to be sorted
+ * @param Sort $sort the sort definition
+ * @return array the sorted data models
+ */
+ protected function sortModels($models, $sort)
+ {
+ $orders = $sort->getOrders();
+ if (!empty($orders)) {
+ ArrayHelper::multisort($models, array_keys($orders), array_values($orders));
+ }
+
+ return $models;
+ }
}
diff --git a/framework/data/BaseDataProvider.php b/framework/data/BaseDataProvider.php
index 31acc2d263c..6c9c395322e 100644
--- a/framework/data/BaseDataProvider.php
+++ b/framework/data/BaseDataProvider.php
@@ -30,221 +30,225 @@
*/
abstract class BaseDataProvider extends Component implements DataProviderInterface
{
- /**
- * @var string an ID that uniquely identifies the data provider among all data providers.
- * You should set this property if the same page contains two or more different data providers.
- * Otherwise, the [[pagination]] and [[sort]] mainly not work properly.
- */
- public $id;
-
- private $_sort;
- private $_pagination;
- private $_keys;
- private $_models;
- private $_totalCount;
-
-
- /**
- * Prepares the data models that will be made available in the current page.
- * @return array the available data models
- */
- abstract protected function prepareModels();
-
- /**
- * Prepares the keys associated with the currently available data models.
- * @param array $models the available data models
- * @return array the keys
- */
- abstract protected function prepareKeys($models);
-
- /**
- * Returns a value indicating the total number of data models in this data provider.
- * @return integer total number of data models in this data provider.
- */
- abstract protected function prepareTotalCount();
-
- /**
- * Prepares the data models and keys.
- *
- * This method will prepare the data models and keys that can be retrieved via
- * [[getModels()]] and [[getKeys()]].
- *
- * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
- *
- * @param boolean $forcePrepare whether to force data preparation even if it has been done before.
- */
- public function prepare($forcePrepare = false)
- {
- if ($forcePrepare || $this->_models === null) {
- $this->_models = $this->prepareModels();
- }
- if ($forcePrepare || $this->_keys === null) {
- $this->_keys = $this->prepareKeys($this->_models);
- }
- }
-
- /**
- * Returns the data models in the current page.
- * @return array the list of data models in the current page.
- */
- public function getModels()
- {
- $this->prepare();
- return $this->_models;
- }
-
- /**
- * Sets the data models in the current page.
- * @param array $models the models in the current page
- */
- public function setModels($models)
- {
- $this->_models = $models;
- }
-
- /**
- * Returns the key values associated with the data models.
- * @return array the list of key values corresponding to [[models]]. Each data model in [[models]]
- * is uniquely identified by the corresponding key value in this array.
- */
- public function getKeys()
- {
- $this->prepare();
- return $this->_keys;
- }
-
- /**
- * Sets the key values associated with the data models.
- * @param array $keys the list of key values corresponding to [[models]].
- */
- public function setKeys($keys)
- {
- $this->_keys = $keys;
- }
-
- /**
- * Returns the number of data models in the current page.
- * @return integer the number of data models in the current page.
- */
- public function getCount()
- {
- return count($this->getModels());
- }
-
- /**
- * Returns the total number of data models.
- * When [[pagination]] is false, this returns the same value as [[count]].
- * Otherwise, it will call [[prepareTotalCount()]] to get the count.
- * @return integer total number of possible data models.
- */
- public function getTotalCount()
- {
- if ($this->getPagination() === false) {
- return $this->getCount();
- } elseif ($this->_totalCount === null) {
- $this->_totalCount = $this->prepareTotalCount();
- }
- return $this->_totalCount;
- }
-
- /**
- * Sets the total number of data models.
- * @param integer $value the total number of data models.
- */
- public function setTotalCount($value)
- {
- $this->_totalCount = $value;
- }
-
- /**
- * Returns the pagination object used by this data provider.
- * Note that you should call [[prepare()]] or [[getModels()]] first to get correct values
- * of [[Pagination::totalCount]] and [[Pagination::pageCount]].
- * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled.
- */
- public function getPagination()
- {
- if ($this->_pagination === null) {
- $this->setPagination([]);
- }
- return $this->_pagination;
- }
-
- /**
- * Sets the pagination for this data provider.
- * @param array|Pagination|boolean $value the pagination to be used by this data provider.
- * This can be one of the following:
- *
- * - a configuration array for creating the pagination object. The "class" element defaults
- * to 'yii\data\Pagination'
- * - an instance of [[Pagination]] or its subclass
- * - false, if pagination needs to be disabled.
- *
- * @throws InvalidParamException
- */
- public function setPagination($value)
- {
- if (is_array($value)) {
- $config = ['class' => Pagination::className()];
- if ($this->id !== null) {
- $config['pageParam'] = $this->id . '-page';
- $config['pageSizeParam'] = $this->id . '-per-page';
- }
- $this->_pagination = Yii::createObject(array_merge($config, $value));
- } elseif ($value instanceof Pagination || $value === false) {
- $this->_pagination = $value;
- } else {
- throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.');
- }
- }
-
- /**
- * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled.
- */
- public function getSort()
- {
- if ($this->_sort === null) {
- $this->setSort([]);
- }
- return $this->_sort;
- }
-
- /**
- * Sets the sort definition for this data provider.
- * @param array|Sort|boolean $value the sort definition to be used by this data provider.
- * This can be one of the following:
- *
- * - a configuration array for creating the sort definition object. The "class" element defaults
- * to 'yii\data\Sort'
- * - an instance of [[Sort]] or its subclass
- * - false, if sorting needs to be disabled.
- *
- * @throws InvalidParamException
- */
- public function setSort($value)
- {
- if (is_array($value)) {
- $config = ['class' => Sort::className()];
- if ($this->id !== null) {
- $config['sortParam'] = $this->id . '-sort';
- }
- $this->_sort = Yii::createObject(array_merge($config, $value));
- } elseif ($value instanceof Sort || $value === false) {
- $this->_sort = $value;
- } else {
- throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.');
- }
- }
-
- /**
- * Refreshes the data provider.
- * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again,
- * they will re-execute the query and return the latest data available.
- */
- public function refresh()
- {
- $this->_totalCount = null;
- $this->_models = null;
- $this->_keys = null;
- }
+ /**
+ * @var string an ID that uniquely identifies the data provider among all data providers.
+ * You should set this property if the same page contains two or more different data providers.
+ * Otherwise, the [[pagination]] and [[sort]] mainly not work properly.
+ */
+ public $id;
+
+ private $_sort;
+ private $_pagination;
+ private $_keys;
+ private $_models;
+ private $_totalCount;
+
+ /**
+ * Prepares the data models that will be made available in the current page.
+ * @return array the available data models
+ */
+ abstract protected function prepareModels();
+
+ /**
+ * Prepares the keys associated with the currently available data models.
+ * @param array $models the available data models
+ * @return array the keys
+ */
+ abstract protected function prepareKeys($models);
+
+ /**
+ * Returns a value indicating the total number of data models in this data provider.
+ * @return integer total number of data models in this data provider.
+ */
+ abstract protected function prepareTotalCount();
+
+ /**
+ * Prepares the data models and keys.
+ *
+ * This method will prepare the data models and keys that can be retrieved via
+ * [[getModels()]] and [[getKeys()]].
+ *
+ * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
+ *
+ * @param boolean $forcePrepare whether to force data preparation even if it has been done before.
+ */
+ public function prepare($forcePrepare = false)
+ {
+ if ($forcePrepare || $this->_models === null) {
+ $this->_models = $this->prepareModels();
+ }
+ if ($forcePrepare || $this->_keys === null) {
+ $this->_keys = $this->prepareKeys($this->_models);
+ }
+ }
+
+ /**
+ * Returns the data models in the current page.
+ * @return array the list of data models in the current page.
+ */
+ public function getModels()
+ {
+ $this->prepare();
+
+ return $this->_models;
+ }
+
+ /**
+ * Sets the data models in the current page.
+ * @param array $models the models in the current page
+ */
+ public function setModels($models)
+ {
+ $this->_models = $models;
+ }
+
+ /**
+ * Returns the key values associated with the data models.
+ * @return array the list of key values corresponding to [[models]]. Each data model in [[models]]
+ * is uniquely identified by the corresponding key value in this array.
+ */
+ public function getKeys()
+ {
+ $this->prepare();
+
+ return $this->_keys;
+ }
+
+ /**
+ * Sets the key values associated with the data models.
+ * @param array $keys the list of key values corresponding to [[models]].
+ */
+ public function setKeys($keys)
+ {
+ $this->_keys = $keys;
+ }
+
+ /**
+ * Returns the number of data models in the current page.
+ * @return integer the number of data models in the current page.
+ */
+ public function getCount()
+ {
+ return count($this->getModels());
+ }
+
+ /**
+ * Returns the total number of data models.
+ * When [[pagination]] is false, this returns the same value as [[count]].
+ * Otherwise, it will call [[prepareTotalCount()]] to get the count.
+ * @return integer total number of possible data models.
+ */
+ public function getTotalCount()
+ {
+ if ($this->getPagination() === false) {
+ return $this->getCount();
+ } elseif ($this->_totalCount === null) {
+ $this->_totalCount = $this->prepareTotalCount();
+ }
+
+ return $this->_totalCount;
+ }
+
+ /**
+ * Sets the total number of data models.
+ * @param integer $value the total number of data models.
+ */
+ public function setTotalCount($value)
+ {
+ $this->_totalCount = $value;
+ }
+
+ /**
+ * Returns the pagination object used by this data provider.
+ * Note that you should call [[prepare()]] or [[getModels()]] first to get correct values
+ * of [[Pagination::totalCount]] and [[Pagination::pageCount]].
+ * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled.
+ */
+ public function getPagination()
+ {
+ if ($this->_pagination === null) {
+ $this->setPagination([]);
+ }
+
+ return $this->_pagination;
+ }
+
+ /**
+ * Sets the pagination for this data provider.
+ * @param array|Pagination|boolean $value the pagination to be used by this data provider.
+ * This can be one of the following:
+ *
+ * - a configuration array for creating the pagination object. The "class" element defaults
+ * to 'yii\data\Pagination'
+ * - an instance of [[Pagination]] or its subclass
+ * - false, if pagination needs to be disabled.
+ *
+ * @throws InvalidParamException
+ */
+ public function setPagination($value)
+ {
+ if (is_array($value)) {
+ $config = ['class' => Pagination::className()];
+ if ($this->id !== null) {
+ $config['pageParam'] = $this->id . '-page';
+ $config['pageSizeParam'] = $this->id . '-per-page';
+ }
+ $this->_pagination = Yii::createObject(array_merge($config, $value));
+ } elseif ($value instanceof Pagination || $value === false) {
+ $this->_pagination = $value;
+ } else {
+ throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.');
+ }
+ }
+
+ /**
+ * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled.
+ */
+ public function getSort()
+ {
+ if ($this->_sort === null) {
+ $this->setSort([]);
+ }
+
+ return $this->_sort;
+ }
+
+ /**
+ * Sets the sort definition for this data provider.
+ * @param array|Sort|boolean $value the sort definition to be used by this data provider.
+ * This can be one of the following:
+ *
+ * - a configuration array for creating the sort definition object. The "class" element defaults
+ * to 'yii\data\Sort'
+ * - an instance of [[Sort]] or its subclass
+ * - false, if sorting needs to be disabled.
+ *
+ * @throws InvalidParamException
+ */
+ public function setSort($value)
+ {
+ if (is_array($value)) {
+ $config = ['class' => Sort::className()];
+ if ($this->id !== null) {
+ $config['sortParam'] = $this->id . '-sort';
+ }
+ $this->_sort = Yii::createObject(array_merge($config, $value));
+ } elseif ($value instanceof Sort || $value === false) {
+ $this->_sort = $value;
+ } else {
+ throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.');
+ }
+ }
+
+ /**
+ * Refreshes the data provider.
+ * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again,
+ * they will re-execute the query and return the latest data available.
+ */
+ public function refresh()
+ {
+ $this->_totalCount = null;
+ $this->_models = null;
+ $this->_keys = null;
+ }
}
diff --git a/framework/data/DataProviderInterface.php b/framework/data/DataProviderInterface.php
index cce3b2dab2b..400bba0509b 100644
--- a/framework/data/DataProviderInterface.php
+++ b/framework/data/DataProviderInterface.php
@@ -18,53 +18,53 @@
*/
interface DataProviderInterface
{
- /**
- * Prepares the data models and keys.
- *
- * This method will prepare the data models and keys that can be retrieved via
- * [[getModels()]] and [[getKeys()]].
- *
- * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
- *
- * @param boolean $forcePrepare whether to force data preparation even if it has been done before.
- */
- public function prepare($forcePrepare = false);
+ /**
+ * Prepares the data models and keys.
+ *
+ * This method will prepare the data models and keys that can be retrieved via
+ * [[getModels()]] and [[getKeys()]].
+ *
+ * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
+ *
+ * @param boolean $forcePrepare whether to force data preparation even if it has been done before.
+ */
+ public function prepare($forcePrepare = false);
- /**
- * Returns the number of data models in the current page.
- * This is equivalent to `count($provider->getModels())`.
- * When [[getPagination|pagination]] is false, this is the same as [[getTotalCount|totalCount]].
- * @return integer the number of data models in the current page.
- */
- public function getCount();
+ /**
+ * Returns the number of data models in the current page.
+ * This is equivalent to `count($provider->getModels())`.
+ * When [[getPagination|pagination]] is false, this is the same as [[getTotalCount|totalCount]].
+ * @return integer the number of data models in the current page.
+ */
+ public function getCount();
- /**
- * Returns the total number of data models.
- * When [[getPagination|pagination]] is false, this is the same as [[getCount|count]].
- * @return integer total number of possible data models.
- */
- public function getTotalCount();
+ /**
+ * Returns the total number of data models.
+ * When [[getPagination|pagination]] is false, this is the same as [[getCount|count]].
+ * @return integer total number of possible data models.
+ */
+ public function getTotalCount();
- /**
- * Returns the data models in the current page.
- * @return array the list of data models in the current page.
- */
- public function getModels();
+ /**
+ * Returns the data models in the current page.
+ * @return array the list of data models in the current page.
+ */
+ public function getModels();
- /**
- * Returns the key values associated with the data models.
- * @return array the list of key values corresponding to [[getModels|models]]. Each data model in [[getModels|models]]
- * is uniquely identified by the corresponding key value in this array.
- */
- public function getKeys();
+ /**
+ * Returns the key values associated with the data models.
+ * @return array the list of key values corresponding to [[getModels|models]]. Each data model in [[getModels|models]]
+ * is uniquely identified by the corresponding key value in this array.
+ */
+ public function getKeys();
- /**
- * @return Sort the sorting object. If this is false, it means the sorting is disabled.
- */
- public function getSort();
+ /**
+ * @return Sort the sorting object. If this is false, it means the sorting is disabled.
+ */
+ public function getSort();
- /**
- * @return Pagination the pagination object. If this is false, it means the pagination is disabled.
- */
- public function getPagination();
+ /**
+ * @return Pagination the pagination object. If this is false, it means the pagination is disabled.
+ */
+ public function getPagination();
}
diff --git a/framework/data/Pagination.php b/framework/data/Pagination.php
index a5ef86ea208..5bbb5159c45 100644
--- a/framework/data/Pagination.php
+++ b/framework/data/Pagination.php
@@ -72,265 +72,271 @@
*/
class Pagination extends Object implements Linkable
{
- const LINK_NEXT = 'next';
- const LINK_PREV = 'prev';
- const LINK_FIRST = 'first';
- const LINK_LAST = 'last';
+ const LINK_NEXT = 'next';
+ const LINK_PREV = 'prev';
+ const LINK_FIRST = 'first';
+ const LINK_LAST = 'last';
- /**
- * @var string name of the parameter storing the current page index.
- * @see params
- */
- public $pageParam = 'page';
- /**
- * @var string name of the parameter storing the page size.
- * @see params
- */
- public $pageSizeParam = 'per-page';
- /**
- * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]].
- * If false and [[page]] is 0, the page parameter will not be put in the URL.
- */
- public $forcePageParam = true;
- /**
- * @var string the route of the controller action for displaying the paged contents.
- * If not set, it means using the currently requested route.
- */
- public $route;
- /**
- * @var array parameters (name => value) that should be used to obtain the current page number
- * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead.
- *
- * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
- *
- * The array element indexed by [[pageParam]] is considered to be the current page number (defaults to 0);
- * while the element indexed by [[pageSizeParam]] is treated as the page size (defaults to [[defaultPageSize]]).
- */
- public $params;
- /**
- * @var \yii\web\UrlManager the URL manager used for creating pagination URLs. If not set,
- * the "urlManager" application component will be used.
- */
- public $urlManager;
- /**
- * @var boolean whether to check if [[page]] is within valid range.
- * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1).
- * Because [[pageCount]] relies on the correct value of [[totalCount]] which may not be available
- * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page
- * number validation. By doing so, [[page]] will return the value indexed by [[pageParam]] in [[params]].
- */
- public $validatePage = true;
- /**
- * @var integer total number of items.
- */
- public $totalCount = 0;
- /**
- * @var integer the default page size. This property will be returned by [[pageSize]] when page size
- * cannot be determined by [[pageSizeParam]] from [[params]].
- */
- public $defaultPageSize = 20;
- /**
- * @var array|boolean the page size limits. The first array element stands for the minimal page size, and the second
- * the maximal page size. If this is false, it means [[pageSize]] should always return the value of [[defaultPageSize]].
- */
- public $pageSizeLimit = [1, 50];
- /**
- * @var integer number of items on each page.
- * If it is less than 1, it means the page size is infinite, and thus a single page contains all items.
- */
- private $_pageSize;
+ /**
+ * @var string name of the parameter storing the current page index.
+ * @see params
+ */
+ public $pageParam = 'page';
+ /**
+ * @var string name of the parameter storing the page size.
+ * @see params
+ */
+ public $pageSizeParam = 'per-page';
+ /**
+ * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]].
+ * If false and [[page]] is 0, the page parameter will not be put in the URL.
+ */
+ public $forcePageParam = true;
+ /**
+ * @var string the route of the controller action for displaying the paged contents.
+ * If not set, it means using the currently requested route.
+ */
+ public $route;
+ /**
+ * @var array parameters (name => value) that should be used to obtain the current page number
+ * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead.
+ *
+ * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
+ *
+ * The array element indexed by [[pageParam]] is considered to be the current page number (defaults to 0);
+ * while the element indexed by [[pageSizeParam]] is treated as the page size (defaults to [[defaultPageSize]]).
+ */
+ public $params;
+ /**
+ * @var \yii\web\UrlManager the URL manager used for creating pagination URLs. If not set,
+ * the "urlManager" application component will be used.
+ */
+ public $urlManager;
+ /**
+ * @var boolean whether to check if [[page]] is within valid range.
+ * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1).
+ * Because [[pageCount]] relies on the correct value of [[totalCount]] which may not be available
+ * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page
+ * number validation. By doing so, [[page]] will return the value indexed by [[pageParam]] in [[params]].
+ */
+ public $validatePage = true;
+ /**
+ * @var integer total number of items.
+ */
+ public $totalCount = 0;
+ /**
+ * @var integer the default page size. This property will be returned by [[pageSize]] when page size
+ * cannot be determined by [[pageSizeParam]] from [[params]].
+ */
+ public $defaultPageSize = 20;
+ /**
+ * @var array|boolean the page size limits. The first array element stands for the minimal page size, and the second
+ * the maximal page size. If this is false, it means [[pageSize]] should always return the value of [[defaultPageSize]].
+ */
+ public $pageSizeLimit = [1, 50];
+ /**
+ * @var integer number of items on each page.
+ * If it is less than 1, it means the page size is infinite, and thus a single page contains all items.
+ */
+ private $_pageSize;
+ /**
+ * @return integer number of pages
+ */
+ public function getPageCount()
+ {
+ $pageSize = $this->getPageSize();
+ if ($pageSize < 1) {
+ return $this->totalCount > 0 ? 1 : 0;
+ } else {
+ $totalCount = $this->totalCount < 0 ? 0 : (int) $this->totalCount;
- /**
- * @return integer number of pages
- */
- public function getPageCount()
- {
- $pageSize = $this->getPageSize();
- if ($pageSize < 1) {
- return $this->totalCount > 0 ? 1 : 0;
- } else {
- $totalCount = $this->totalCount < 0 ? 0 : (int)$this->totalCount;
- return (int)(($totalCount + $pageSize - 1) / $pageSize);
- }
- }
+ return (int) (($totalCount + $pageSize - 1) / $pageSize);
+ }
+ }
- private $_page;
+ private $_page;
- /**
- * Returns the zero-based current page number.
- * @param boolean $recalculate whether to recalculate the current page based on the page size and item count.
- * @return integer the zero-based current page number.
- */
- public function getPage($recalculate = false)
- {
- if ($this->_page === null || $recalculate) {
- $page = (int)$this->getQueryParam($this->pageParam, 1) - 1;
- $this->setPage($page, true);
- }
- return $this->_page;
- }
+ /**
+ * Returns the zero-based current page number.
+ * @param boolean $recalculate whether to recalculate the current page based on the page size and item count.
+ * @return integer the zero-based current page number.
+ */
+ public function getPage($recalculate = false)
+ {
+ if ($this->_page === null || $recalculate) {
+ $page = (int) $this->getQueryParam($this->pageParam, 1) - 1;
+ $this->setPage($page, true);
+ }
- /**
- * Sets the current page number.
- * @param integer $value the zero-based index of the current page.
- * @param boolean $validatePage whether to validate the page number. Note that in order
- * to validate the page number, both [[validatePage]] and this parameter must be true.
- */
- public function setPage($value, $validatePage = false)
- {
- if ($value === null) {
- $this->_page = null;
- } else {
- $value = (int)$value;
- if ($validatePage && $this->validatePage) {
- $pageCount = $this->getPageCount();
- if ($value >= $pageCount) {
- $value = $pageCount - 1;
- }
- }
- if ($value < 0) {
- $value = 0;
- }
- $this->_page = $value;
- }
- }
+ return $this->_page;
+ }
- /**
- * Returns the number of items per page.
- * By default, this method will try to determine the page size by [[pageSizeParam]] in [[params]].
- * If the page size cannot be determined this way, [[defaultPageSize]] will be returned.
- * @return integer the number of items per page.
- * @see pageSizeLimit
- */
- public function getPageSize()
- {
- if ($this->_pageSize === null) {
- if (empty($this->pageSizeLimit)) {
- $pageSize = $this->defaultPageSize;
- $this->setPageSize($pageSize);
- } else {
- $pageSize = (int)$this->getQueryParam($this->pageSizeParam, $this->defaultPageSize);
- $this->setPageSize($pageSize, true);
- }
- }
- return $this->_pageSize;
- }
+ /**
+ * Sets the current page number.
+ * @param integer $value the zero-based index of the current page.
+ * @param boolean $validatePage whether to validate the page number. Note that in order
+ * to validate the page number, both [[validatePage]] and this parameter must be true.
+ */
+ public function setPage($value, $validatePage = false)
+ {
+ if ($value === null) {
+ $this->_page = null;
+ } else {
+ $value = (int) $value;
+ if ($validatePage && $this->validatePage) {
+ $pageCount = $this->getPageCount();
+ if ($value >= $pageCount) {
+ $value = $pageCount - 1;
+ }
+ }
+ if ($value < 0) {
+ $value = 0;
+ }
+ $this->_page = $value;
+ }
+ }
- /**
- * @param integer $value the number of items per page.
- * @param boolean $validatePageSize whether to validate page size.
- */
- public function setPageSize($value, $validatePageSize = false)
- {
- if ($value === null) {
- $this->_pageSize = null;
- } else {
- $value = (int)$value;
- if ($validatePageSize && count($this->pageSizeLimit) === 2 && isset($this->pageSizeLimit[0], $this->pageSizeLimit[1])) {
- if ($value < $this->pageSizeLimit[0]) {
- $value = $this->pageSizeLimit[0];
- } elseif ($value > $this->pageSizeLimit[1]) {
- $value = $this->pageSizeLimit[1];
- }
- }
- $this->_pageSize = $value;
- }
- }
+ /**
+ * Returns the number of items per page.
+ * By default, this method will try to determine the page size by [[pageSizeParam]] in [[params]].
+ * If the page size cannot be determined this way, [[defaultPageSize]] will be returned.
+ * @return integer the number of items per page.
+ * @see pageSizeLimit
+ */
+ public function getPageSize()
+ {
+ if ($this->_pageSize === null) {
+ if (empty($this->pageSizeLimit)) {
+ $pageSize = $this->defaultPageSize;
+ $this->setPageSize($pageSize);
+ } else {
+ $pageSize = (int) $this->getQueryParam($this->pageSizeParam, $this->defaultPageSize);
+ $this->setPageSize($pageSize, true);
+ }
+ }
- /**
- * Creates the URL suitable for pagination with the specified page number.
- * This method is mainly called by pagers when creating URLs used to perform pagination.
- * @param integer $page the zero-based page number that the URL should point to.
- * @param boolean $absolute whether to create an absolute URL. Defaults to `false`.
- * @return string the created URL
- * @see params
- * @see forcePageParam
- */
- public function createUrl($page, $absolute = false)
- {
- if (($params = $this->params) === null) {
- $request = Yii::$app->getRequest();
- $params = $request instanceof Request ? $request->getQueryParams() : [];
- }
- if ($page > 0 || $page >= 0 && $this->forcePageParam) {
- $params[$this->pageParam] = $page + 1;
- } else {
- unset($params[$this->pageParam]);
- }
- $pageSize = $this->getPageSize();
- if ($pageSize != $this->defaultPageSize) {
- $params[$this->pageSizeParam] = $pageSize;
- } else {
- unset($params[$this->pageSizeParam]);
- }
- $params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
- $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
- if ($absolute) {
- return $urlManager->createAbsoluteUrl($params);
- } else {
- return $urlManager->createUrl($params);
- }
- }
+ return $this->_pageSize;
+ }
- /**
- * @return integer the offset of the data. This may be used to set the
- * OFFSET value for a SQL statement for fetching the current page of data.
- */
- public function getOffset()
- {
- $pageSize = $this->getPageSize();
- return $pageSize < 1 ? 0 : $this->getPage() * $pageSize;
- }
+ /**
+ * @param integer $value the number of items per page.
+ * @param boolean $validatePageSize whether to validate page size.
+ */
+ public function setPageSize($value, $validatePageSize = false)
+ {
+ if ($value === null) {
+ $this->_pageSize = null;
+ } else {
+ $value = (int) $value;
+ if ($validatePageSize && count($this->pageSizeLimit) === 2 && isset($this->pageSizeLimit[0], $this->pageSizeLimit[1])) {
+ if ($value < $this->pageSizeLimit[0]) {
+ $value = $this->pageSizeLimit[0];
+ } elseif ($value > $this->pageSizeLimit[1]) {
+ $value = $this->pageSizeLimit[1];
+ }
+ }
+ $this->_pageSize = $value;
+ }
+ }
- /**
- * @return integer the limit of the data. This may be used to set the
- * LIMIT value for a SQL statement for fetching the current page of data.
- * Note that if the page size is infinite, a value -1 will be returned.
- */
- public function getLimit()
- {
- $pageSize = $this->getPageSize();
- return $pageSize < 1 ? -1 : $pageSize;
- }
+ /**
+ * Creates the URL suitable for pagination with the specified page number.
+ * This method is mainly called by pagers when creating URLs used to perform pagination.
+ * @param integer $page the zero-based page number that the URL should point to.
+ * @param boolean $absolute whether to create an absolute URL. Defaults to `false`.
+ * @return string the created URL
+ * @see params
+ * @see forcePageParam
+ */
+ public function createUrl($page, $absolute = false)
+ {
+ if (($params = $this->params) === null) {
+ $request = Yii::$app->getRequest();
+ $params = $request instanceof Request ? $request->getQueryParams() : [];
+ }
+ if ($page > 0 || $page >= 0 && $this->forcePageParam) {
+ $params[$this->pageParam] = $page + 1;
+ } else {
+ unset($params[$this->pageParam]);
+ }
+ $pageSize = $this->getPageSize();
+ if ($pageSize != $this->defaultPageSize) {
+ $params[$this->pageSizeParam] = $pageSize;
+ } else {
+ unset($params[$this->pageSizeParam]);
+ }
+ $params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
+ $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
+ if ($absolute) {
+ return $urlManager->createAbsoluteUrl($params);
+ } else {
+ return $urlManager->createUrl($params);
+ }
+ }
- /**
- * Returns a whole set of links for navigating to the first, last, next and previous pages.
- * @param boolean $absolute whether the generated URLs should be absolute.
- * @return array the links for navigational purpose. The array keys specify the purpose of the links (e.g. [[LINK_FIRST]]),
- * and the array values are the corresponding URLs.
- */
- public function getLinks($absolute = false)
- {
- $currentPage = $this->getPage();
- $pageCount = $this->getPageCount();
- $links = [
- Link::REL_SELF => $this->createUrl($currentPage, $absolute),
- ];
- if ($currentPage > 0) {
- $links[self::LINK_FIRST] = $this->createUrl(0, $absolute);
- $links[self::LINK_PREV] = $this->createUrl($currentPage - 1, $absolute);
- }
- if ($currentPage < $pageCount - 1) {
- $links[self::LINK_NEXT] = $this->createUrl($currentPage + 1, $absolute);
- $links[self::LINK_LAST] = $this->createUrl($pageCount - 1, $absolute);
- }
- return $links;
- }
+ /**
+ * @return integer the offset of the data. This may be used to set the
+ * OFFSET value for a SQL statement for fetching the current page of data.
+ */
+ public function getOffset()
+ {
+ $pageSize = $this->getPageSize();
- /**
- * Returns the value of the specified query parameter.
- * This method returns the named parameter value from [[params]]. Null is returned if the value does not exist.
- * @param string $name the parameter name
- * @param string $defaultValue the value to be returned when the specified parameter does not exist in [[params]].
- * @return string the parameter value
- */
- protected function getQueryParam($name, $defaultValue = null)
- {
- if (($params = $this->params) === null) {
- $request = Yii::$app->getRequest();
- $params = $request instanceof Request ? $request->getQueryParams() : [];
- }
- return isset($params[$name]) && is_scalar($params[$name]) ? $params[$name] : $defaultValue;
- }
+ return $pageSize < 1 ? 0 : $this->getPage() * $pageSize;
+ }
+
+ /**
+ * @return integer the limit of the data. This may be used to set the
+ * LIMIT value for a SQL statement for fetching the current page of data.
+ * Note that if the page size is infinite, a value -1 will be returned.
+ */
+ public function getLimit()
+ {
+ $pageSize = $this->getPageSize();
+
+ return $pageSize < 1 ? -1 : $pageSize;
+ }
+
+ /**
+ * Returns a whole set of links for navigating to the first, last, next and previous pages.
+ * @param boolean $absolute whether the generated URLs should be absolute.
+ * @return array the links for navigational purpose. The array keys specify the purpose of the links (e.g. [[LINK_FIRST]]),
+ * and the array values are the corresponding URLs.
+ */
+ public function getLinks($absolute = false)
+ {
+ $currentPage = $this->getPage();
+ $pageCount = $this->getPageCount();
+ $links = [
+ Link::REL_SELF => $this->createUrl($currentPage, $absolute),
+ ];
+ if ($currentPage > 0) {
+ $links[self::LINK_FIRST] = $this->createUrl(0, $absolute);
+ $links[self::LINK_PREV] = $this->createUrl($currentPage - 1, $absolute);
+ }
+ if ($currentPage < $pageCount - 1) {
+ $links[self::LINK_NEXT] = $this->createUrl($currentPage + 1, $absolute);
+ $links[self::LINK_LAST] = $this->createUrl($pageCount - 1, $absolute);
+ }
+
+ return $links;
+ }
+
+ /**
+ * Returns the value of the specified query parameter.
+ * This method returns the named parameter value from [[params]]. Null is returned if the value does not exist.
+ * @param string $name the parameter name
+ * @param string $defaultValue the value to be returned when the specified parameter does not exist in [[params]].
+ * @return string the parameter value
+ */
+ protected function getQueryParam($name, $defaultValue = null)
+ {
+ if (($params = $this->params) === null) {
+ $request = Yii::$app->getRequest();
+ $params = $request instanceof Request ? $request->getQueryParams() : [];
+ }
+
+ return isset($params[$name]) && is_scalar($params[$name]) ? $params[$name] : $defaultValue;
+ }
}
diff --git a/framework/data/Sort.php b/framework/data/Sort.php
index cfcbb82a392..dec5dedb981 100644
--- a/framework/data/Sort.php
+++ b/framework/data/Sort.php
@@ -76,316 +76,321 @@
*/
class Sort extends Object
{
- /**
- * @var boolean whether the sorting can be applied to multiple attributes simultaneously.
- * Defaults to false, which means each time the data can only be sorted by one attribute.
- */
- public $enableMultiSort = false;
+ /**
+ * @var boolean whether the sorting can be applied to multiple attributes simultaneously.
+ * Defaults to false, which means each time the data can only be sorted by one attribute.
+ */
+ public $enableMultiSort = false;
- /**
- * @var array list of attributes that are allowed to be sorted. Its syntax can be
- * described using the following example:
- *
- * ~~~
- * [
- * 'age',
- * 'name' => [
- * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
- * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
- * 'default' => SORT_DESC,
- * 'label' => 'Name',
- * ],
- * ]
- * ~~~
- *
- * In the above, two attributes are declared: "age" and "name". The "age" attribute is
- * a simple attribute which is equivalent to the following:
- *
- * ~~~
- * 'age' => [
- * 'asc' => ['age' => SORT_ASC],
- * 'desc' => ['age' => SORT_DESC],
- * 'default' => SORT_ASC,
- * 'label' => Inflector::camel2words('age'),
- * ]
- * ~~~
- *
- * The "name" attribute is a composite attribute:
- *
- * - The "name" key represents the attribute name which will appear in the URLs leading
- * to sort actions.
- * - The "asc" and "desc" elements specify how to sort by the attribute in ascending
- * and descending orders, respectively. Their values represent the actual columns and
- * the directions by which the data should be sorted by.
- * - The "default" element specifies by which direction the attribute should be sorted
- * if it is not currently sorted (the default value is ascending order).
- * - The "label" element specifies what label should be used when calling [[link()]] to create
- * a sort link. If not set, [[Inflector::camel2words()]] will be called to get a label.
- * Note that it will not be HTML-encoded.
- *
- * Note that if the Sort object is already created, you can only use the full format
- * to configure every attribute. Each attribute must include these elements: `asc` and `desc`.
- */
- public $attributes = [];
- /**
- * @var string the name of the parameter that specifies which attributes to be sorted
- * in which direction. Defaults to 'sort'.
- * @see params
- */
- public $sortParam = 'sort';
- /**
- * @var array the order that should be used when the current request does not specify any order.
- * The array keys are attribute names and the array values are the corresponding sort directions. For example,
- *
- * ~~~
- * [
- * 'name' => SORT_ASC,
- * 'created_at' => SORT_DESC,
- * ]
- * ~~~
- *
- * @see attributeOrders
- */
- public $defaultOrder;
- /**
- * @var string the route of the controller action for displaying the sorted contents.
- * If not set, it means using the currently requested route.
- */
- public $route;
- /**
- * @var string the character used to separate different attributes that need to be sorted by.
- */
- public $separator = ',';
- /**
- * @var array parameters (name => value) that should be used to obtain the current sort directions
- * and to create new sort URLs. If not set, $_GET will be used instead.
- *
- * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
- *
- * The array element indexed by [[sortParam]] is considered to be the current sort directions.
- * If the element does not exist, the [[defaultOrder|default order]] will be used.
- *
- * @see sortParam
- * @see defaultOrder
- */
- public $params;
- /**
- * @var \yii\web\UrlManager the URL manager used for creating sort URLs. If not set,
- * the "urlManager" application component will be used.
- */
- public $urlManager;
+ /**
+ * @var array list of attributes that are allowed to be sorted. Its syntax can be
+ * described using the following example:
+ *
+ * ~~~
+ * [
+ * 'age',
+ * 'name' => [
+ * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
+ * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
+ * 'default' => SORT_DESC,
+ * 'label' => 'Name',
+ * ],
+ * ]
+ * ~~~
+ *
+ * In the above, two attributes are declared: "age" and "name". The "age" attribute is
+ * a simple attribute which is equivalent to the following:
+ *
+ * ~~~
+ * 'age' => [
+ * 'asc' => ['age' => SORT_ASC],
+ * 'desc' => ['age' => SORT_DESC],
+ * 'default' => SORT_ASC,
+ * 'label' => Inflector::camel2words('age'),
+ * ]
+ * ~~~
+ *
+ * The "name" attribute is a composite attribute:
+ *
+ * - The "name" key represents the attribute name which will appear in the URLs leading
+ * to sort actions.
+ * - The "asc" and "desc" elements specify how to sort by the attribute in ascending
+ * and descending orders, respectively. Their values represent the actual columns and
+ * the directions by which the data should be sorted by.
+ * - The "default" element specifies by which direction the attribute should be sorted
+ * if it is not currently sorted (the default value is ascending order).
+ * - The "label" element specifies what label should be used when calling [[link()]] to create
+ * a sort link. If not set, [[Inflector::camel2words()]] will be called to get a label.
+ * Note that it will not be HTML-encoded.
+ *
+ * Note that if the Sort object is already created, you can only use the full format
+ * to configure every attribute. Each attribute must include these elements: `asc` and `desc`.
+ */
+ public $attributes = [];
+ /**
+ * @var string the name of the parameter that specifies which attributes to be sorted
+ * in which direction. Defaults to 'sort'.
+ * @see params
+ */
+ public $sortParam = 'sort';
+ /**
+ * @var array the order that should be used when the current request does not specify any order.
+ * The array keys are attribute names and the array values are the corresponding sort directions. For example,
+ *
+ * ~~~
+ * [
+ * 'name' => SORT_ASC,
+ * 'created_at' => SORT_DESC,
+ * ]
+ * ~~~
+ *
+ * @see attributeOrders
+ */
+ public $defaultOrder;
+ /**
+ * @var string the route of the controller action for displaying the sorted contents.
+ * If not set, it means using the currently requested route.
+ */
+ public $route;
+ /**
+ * @var string the character used to separate different attributes that need to be sorted by.
+ */
+ public $separator = ',';
+ /**
+ * @var array parameters (name => value) that should be used to obtain the current sort directions
+ * and to create new sort URLs. If not set, $_GET will be used instead.
+ *
+ * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
+ *
+ * The array element indexed by [[sortParam]] is considered to be the current sort directions.
+ * If the element does not exist, the [[defaultOrder|default order]] will be used.
+ *
+ * @see sortParam
+ * @see defaultOrder
+ */
+ public $params;
+ /**
+ * @var \yii\web\UrlManager the URL manager used for creating sort URLs. If not set,
+ * the "urlManager" application component will be used.
+ */
+ public $urlManager;
- /**
- * Normalizes the [[attributes]] property.
- */
- public function init()
- {
- $attributes = [];
- foreach ($this->attributes as $name => $attribute) {
- if (!is_array($attribute)) {
- $attributes[$attribute] = [
- 'asc' => [$attribute => SORT_ASC],
- 'desc' => [$attribute => SORT_DESC],
- ];
- } elseif (!isset($attribute['asc'], $attribute['desc'])) {
- $attributes[$name] = array_merge([
- 'asc' => [$name => SORT_ASC],
- 'desc' => [$name => SORT_DESC],
- ], $attribute);
- } else {
- $attributes[$name] = $attribute;
- }
- }
- $this->attributes = $attributes;
- }
+ /**
+ * Normalizes the [[attributes]] property.
+ */
+ public function init()
+ {
+ $attributes = [];
+ foreach ($this->attributes as $name => $attribute) {
+ if (!is_array($attribute)) {
+ $attributes[$attribute] = [
+ 'asc' => [$attribute => SORT_ASC],
+ 'desc' => [$attribute => SORT_DESC],
+ ];
+ } elseif (!isset($attribute['asc'], $attribute['desc'])) {
+ $attributes[$name] = array_merge([
+ 'asc' => [$name => SORT_ASC],
+ 'desc' => [$name => SORT_DESC],
+ ], $attribute);
+ } else {
+ $attributes[$name] = $attribute;
+ }
+ }
+ $this->attributes = $attributes;
+ }
- /**
- * Returns the columns and their corresponding sort directions.
- * @param boolean $recalculate whether to recalculate the sort directions
- * @return array the columns (keys) and their corresponding sort directions (values).
- * This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query.
- */
- public function getOrders($recalculate = false)
- {
- $attributeOrders = $this->getAttributeOrders($recalculate);
- $orders = [];
- foreach ($attributeOrders as $attribute => $direction) {
- $definition = $this->attributes[$attribute];
- $columns = $definition[$direction === SORT_ASC ? 'asc' : 'desc'];
- foreach ($columns as $name => $dir) {
- $orders[$name] = $dir;
- }
- }
- return $orders;
- }
+ /**
+ * Returns the columns and their corresponding sort directions.
+ * @param boolean $recalculate whether to recalculate the sort directions
+ * @return array the columns (keys) and their corresponding sort directions (values).
+ * This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query.
+ */
+ public function getOrders($recalculate = false)
+ {
+ $attributeOrders = $this->getAttributeOrders($recalculate);
+ $orders = [];
+ foreach ($attributeOrders as $attribute => $direction) {
+ $definition = $this->attributes[$attribute];
+ $columns = $definition[$direction === SORT_ASC ? 'asc' : 'desc'];
+ foreach ($columns as $name => $dir) {
+ $orders[$name] = $dir;
+ }
+ }
- /**
- * @var array the currently requested sort order as computed by [[getAttributeOrders]].
- */
- private $_attributeOrders;
+ return $orders;
+ }
- /**
- * Returns the currently requested sort information.
- * @param boolean $recalculate whether to recalculate the sort directions
- * @return array sort directions indexed by attribute names.
- * Sort direction can be either `SORT_ASC` for ascending order or
- * `SORT_DESC` for descending order.
- */
- public function getAttributeOrders($recalculate = false)
- {
- if ($this->_attributeOrders === null || $recalculate) {
- $this->_attributeOrders = [];
- if (($params = $this->params) === null) {
- $request = Yii::$app->getRequest();
- $params = $request instanceof Request ? $request->getQueryParams() : [];
- }
- if (isset($params[$this->sortParam]) && is_scalar($params[$this->sortParam])) {
- $attributes = explode($this->separator, $params[$this->sortParam]);
- foreach ($attributes as $attribute) {
- $descending = false;
- if (strncmp($attribute, '-', 1) === 0) {
- $descending = true;
- $attribute = substr($attribute, 1);
- }
+ /**
+ * @var array the currently requested sort order as computed by [[getAttributeOrders]].
+ */
+ private $_attributeOrders;
- if (isset($this->attributes[$attribute])) {
- $this->_attributeOrders[$attribute] = $descending ? SORT_DESC : SORT_ASC;
- if (!$this->enableMultiSort) {
- return $this->_attributeOrders;
- }
- }
- }
- }
- if (empty($this->_attributeOrders) && is_array($this->defaultOrder)) {
- $this->_attributeOrders = $this->defaultOrder;
- }
- }
- return $this->_attributeOrders;
- }
+ /**
+ * Returns the currently requested sort information.
+ * @param boolean $recalculate whether to recalculate the sort directions
+ * @return array sort directions indexed by attribute names.
+ * Sort direction can be either `SORT_ASC` for ascending order or
+ * `SORT_DESC` for descending order.
+ */
+ public function getAttributeOrders($recalculate = false)
+ {
+ if ($this->_attributeOrders === null || $recalculate) {
+ $this->_attributeOrders = [];
+ if (($params = $this->params) === null) {
+ $request = Yii::$app->getRequest();
+ $params = $request instanceof Request ? $request->getQueryParams() : [];
+ }
+ if (isset($params[$this->sortParam]) && is_scalar($params[$this->sortParam])) {
+ $attributes = explode($this->separator, $params[$this->sortParam]);
+ foreach ($attributes as $attribute) {
+ $descending = false;
+ if (strncmp($attribute, '-', 1) === 0) {
+ $descending = true;
+ $attribute = substr($attribute, 1);
+ }
- /**
- * Returns the sort direction of the specified attribute in the current request.
- * @param string $attribute the attribute name
- * @return boolean|null Sort direction of the attribute. Can be either `SORT_ASC`
- * for ascending order or `SORT_DESC` for descending order. Null is returned
- * if the attribute is invalid or does not need to be sorted.
- */
- public function getAttributeOrder($attribute)
- {
- $orders = $this->getAttributeOrders();
- return isset($orders[$attribute]) ? $orders[$attribute] : null;
- }
+ if (isset($this->attributes[$attribute])) {
+ $this->_attributeOrders[$attribute] = $descending ? SORT_DESC : SORT_ASC;
+ if (!$this->enableMultiSort) {
+ return $this->_attributeOrders;
+ }
+ }
+ }
+ }
+ if (empty($this->_attributeOrders) && is_array($this->defaultOrder)) {
+ $this->_attributeOrders = $this->defaultOrder;
+ }
+ }
- /**
- * Generates a hyperlink that links to the sort action to sort by the specified attribute.
- * Based on the sort direction, the CSS class of the generated hyperlink will be appended
- * with "asc" or "desc".
- * @param string $attribute the attribute name by which the data should be sorted by.
- * @param array $options additional HTML attributes for the hyperlink tag.
- * There is one special attribute `label` which will be used as the label of the hyperlink.
- * If this is not set, the label defined in [[attributes]] will be used.
- * If no label is defined, [[\yii\helpers\Inflector::camel2words()]] will be called to get a label.
- * Note that it will not be HTML-encoded.
- * @return string the generated hyperlink
- * @throws InvalidConfigException if the attribute is unknown
- */
- public function link($attribute, $options = [])
- {
- if (($direction = $this->getAttributeOrder($attribute)) !== null) {
- $class = $direction === SORT_DESC ? 'desc' : 'asc';
- if (isset($options['class'])) {
- $options['class'] .= ' ' . $class;
- } else {
- $options['class'] = $class;
- }
- }
+ return $this->_attributeOrders;
+ }
- $url = $this->createUrl($attribute);
- $options['data-sort'] = $this->createSortParam($attribute);
+ /**
+ * Returns the sort direction of the specified attribute in the current request.
+ * @param string $attribute the attribute name
+ * @return boolean|null Sort direction of the attribute. Can be either `SORT_ASC`
+ * for ascending order or `SORT_DESC` for descending order. Null is returned
+ * if the attribute is invalid or does not need to be sorted.
+ */
+ public function getAttributeOrder($attribute)
+ {
+ $orders = $this->getAttributeOrders();
- if (isset($options['label'])) {
- $label = $options['label'];
- unset($options['label']);
- } else {
- if (isset($this->attributes[$attribute]['label'])) {
- $label = $this->attributes[$attribute]['label'];
- } else {
- $label = Inflector::camel2words($attribute);
- }
- }
- return Html::a($label, $url, $options);
- }
+ return isset($orders[$attribute]) ? $orders[$attribute] : null;
+ }
- /**
- * Creates a URL for sorting the data by the specified attribute.
- * This method will consider the current sorting status given by [[attributeOrders]].
- * For example, if the current page already sorts the data by the specified attribute in ascending order,
- * then the URL created will lead to a page that sorts the data by the specified attribute in descending order.
- * @param string $attribute the attribute name
- * @param boolean $absolute whether to create an absolute URL. Defaults to `false`.
- * @return string the URL for sorting. False if the attribute is invalid.
- * @throws InvalidConfigException if the attribute is unknown
- * @see attributeOrders
- * @see params
- */
- public function createUrl($attribute, $absolute = false)
- {
- if (($params = $this->params) === null) {
- $request = Yii::$app->getRequest();
- $params = $request instanceof Request ? $request->getQueryParams() : [];
- }
- $params[$this->sortParam] = $this->createSortParam($attribute);
- $params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
- $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
- if ($absolute) {
- return $urlManager->createAbsoluteUrl($params);
- } else {
- return $urlManager->createUrl($params);
- }
- }
+ /**
+ * Generates a hyperlink that links to the sort action to sort by the specified attribute.
+ * Based on the sort direction, the CSS class of the generated hyperlink will be appended
+ * with "asc" or "desc".
+ * @param string $attribute the attribute name by which the data should be sorted by.
+ * @param array $options additional HTML attributes for the hyperlink tag.
+ * There is one special attribute `label` which will be used as the label of the hyperlink.
+ * If this is not set, the label defined in [[attributes]] will be used.
+ * If no label is defined, [[\yii\helpers\Inflector::camel2words()]] will be called to get a label.
+ * Note that it will not be HTML-encoded.
+ * @return string the generated hyperlink
+ * @throws InvalidConfigException if the attribute is unknown
+ */
+ public function link($attribute, $options = [])
+ {
+ if (($direction = $this->getAttributeOrder($attribute)) !== null) {
+ $class = $direction === SORT_DESC ? 'desc' : 'asc';
+ if (isset($options['class'])) {
+ $options['class'] .= ' ' . $class;
+ } else {
+ $options['class'] = $class;
+ }
+ }
- /**
- * Creates the sort variable for the specified attribute.
- * The newly created sort variable can be used to create a URL that will lead to
- * sorting by the specified attribute.
- * @param string $attribute the attribute name
- * @return string the value of the sort variable
- * @throws InvalidConfigException if the specified attribute is not defined in [[attributes]]
- */
- public function createSortParam($attribute)
- {
- if (!isset($this->attributes[$attribute])) {
- throw new InvalidConfigException("Unknown attribute: $attribute");
- }
- $definition = $this->attributes[$attribute];
- $directions = $this->getAttributeOrders();
- if (isset($directions[$attribute])) {
- $direction = $directions[$attribute] === SORT_DESC ? SORT_ASC : SORT_DESC;
- unset($directions[$attribute]);
- } else {
- $direction = isset($definition['default']) ? $definition['default'] : SORT_ASC;
- }
+ $url = $this->createUrl($attribute);
+ $options['data-sort'] = $this->createSortParam($attribute);
- if ($this->enableMultiSort) {
- $directions = array_merge([$attribute => $direction], $directions);
- } else {
- $directions = [$attribute => $direction];
- }
+ if (isset($options['label'])) {
+ $label = $options['label'];
+ unset($options['label']);
+ } else {
+ if (isset($this->attributes[$attribute]['label'])) {
+ $label = $this->attributes[$attribute]['label'];
+ } else {
+ $label = Inflector::camel2words($attribute);
+ }
+ }
- $sorts = [];
- foreach ($directions as $attribute => $direction) {
- $sorts[] = $direction === SORT_DESC ? '-' . $attribute : $attribute;
- }
- return implode($this->separator, $sorts);
- }
+ return Html::a($label, $url, $options);
+ }
- /**
- * Returns a value indicating whether the sort definition supports sorting by the named attribute.
- * @param string $name the attribute name
- * @return boolean whether the sort definition supports sorting by the named attribute.
- */
- public function hasAttribute($name)
- {
- return isset($this->attributes[$name]);
- }
+ /**
+ * Creates a URL for sorting the data by the specified attribute.
+ * This method will consider the current sorting status given by [[attributeOrders]].
+ * For example, if the current page already sorts the data by the specified attribute in ascending order,
+ * then the URL created will lead to a page that sorts the data by the specified attribute in descending order.
+ * @param string $attribute the attribute name
+ * @param boolean $absolute whether to create an absolute URL. Defaults to `false`.
+ * @return string the URL for sorting. False if the attribute is invalid.
+ * @throws InvalidConfigException if the attribute is unknown
+ * @see attributeOrders
+ * @see params
+ */
+ public function createUrl($attribute, $absolute = false)
+ {
+ if (($params = $this->params) === null) {
+ $request = Yii::$app->getRequest();
+ $params = $request instanceof Request ? $request->getQueryParams() : [];
+ }
+ $params[$this->sortParam] = $this->createSortParam($attribute);
+ $params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
+ $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
+ if ($absolute) {
+ return $urlManager->createAbsoluteUrl($params);
+ } else {
+ return $urlManager->createUrl($params);
+ }
+ }
+
+ /**
+ * Creates the sort variable for the specified attribute.
+ * The newly created sort variable can be used to create a URL that will lead to
+ * sorting by the specified attribute.
+ * @param string $attribute the attribute name
+ * @return string the value of the sort variable
+ * @throws InvalidConfigException if the specified attribute is not defined in [[attributes]]
+ */
+ public function createSortParam($attribute)
+ {
+ if (!isset($this->attributes[$attribute])) {
+ throw new InvalidConfigException("Unknown attribute: $attribute");
+ }
+ $definition = $this->attributes[$attribute];
+ $directions = $this->getAttributeOrders();
+ if (isset($directions[$attribute])) {
+ $direction = $directions[$attribute] === SORT_DESC ? SORT_ASC : SORT_DESC;
+ unset($directions[$attribute]);
+ } else {
+ $direction = isset($definition['default']) ? $definition['default'] : SORT_ASC;
+ }
+
+ if ($this->enableMultiSort) {
+ $directions = array_merge([$attribute => $direction], $directions);
+ } else {
+ $directions = [$attribute => $direction];
+ }
+
+ $sorts = [];
+ foreach ($directions as $attribute => $direction) {
+ $sorts[] = $direction === SORT_DESC ? '-' . $attribute : $attribute;
+ }
+
+ return implode($this->separator, $sorts);
+ }
+
+ /**
+ * Returns a value indicating whether the sort definition supports sorting by the named attribute.
+ * @param string $name the attribute name
+ * @return boolean whether the sort definition supports sorting by the named attribute.
+ */
+ public function hasAttribute($name)
+ {
+ return isset($this->attributes[$name]);
+ }
}
diff --git a/framework/data/SqlDataProvider.php b/framework/data/SqlDataProvider.php
index e00ebe20427..090c469fa3f 100644
--- a/framework/data/SqlDataProvider.php
+++ b/framework/data/SqlDataProvider.php
@@ -61,98 +61,98 @@
*/
class SqlDataProvider extends BaseDataProvider
{
- /**
- * @var Connection|string the DB connection object or the application component ID of the DB connection.
- */
- public $db = 'db';
- /**
- * @var string the SQL statement to be used for fetching data rows.
- */
- public $sql;
- /**
- * @var array parameters (name=>value) to be bound to the SQL statement.
- */
- public $params = [];
- /**
- * @var string|callable the column that is used as the key of the data models.
- * This can be either a column name, or a callable that returns the key value of a given data model.
- *
- * If this is not set, the keys of the [[models]] array will be used.
- */
- public $key;
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ */
+ public $db = 'db';
+ /**
+ * @var string the SQL statement to be used for fetching data rows.
+ */
+ public $sql;
+ /**
+ * @var array parameters (name=>value) to be bound to the SQL statement.
+ */
+ public $params = [];
+ /**
+ * @var string|callable the column that is used as the key of the data models.
+ * This can be either a column name, or a callable that returns the key value of a given data model.
+ *
+ * If this is not set, the keys of the [[models]] array will be used.
+ */
+ public $key;
+ /**
+ * Initializes the DB connection component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.');
+ }
+ if ($this->sql === null) {
+ throw new InvalidConfigException('The "sql" property must be set.');
+ }
+ }
- /**
- * Initializes the DB connection component.
- * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
- * @throws InvalidConfigException if [[db]] is invalid.
- */
- public function init()
- {
- parent::init();
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!$this->db instanceof Connection) {
- throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.');
- }
- if ($this->sql === null) {
- throw new InvalidConfigException('The "sql" property must be set.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function prepareModels()
+ {
+ $sql = $this->sql;
+ $qb = $this->db->getQueryBuilder();
+ if (($sort = $this->getSort()) !== false) {
+ $orderBy = $qb->buildOrderBy($sort->getOrders());
+ if (!empty($orderBy)) {
+ $orderBy = substr($orderBy, 9);
+ if (preg_match('/\s+order\s+by\s+[\w\s,\.]+$/i', $sql)) {
+ $sql .= ', ' . $orderBy;
+ } else {
+ $sql .= ' ORDER BY ' . $orderBy;
+ }
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function prepareModels()
- {
- $sql = $this->sql;
- $qb = $this->db->getQueryBuilder();
- if (($sort = $this->getSort()) !== false) {
- $orderBy = $qb->buildOrderBy($sort->getOrders());
- if (!empty($orderBy)) {
- $orderBy = substr($orderBy, 9);
- if (preg_match('/\s+order\s+by\s+[\w\s,\.]+$/i', $sql)) {
- $sql .= ', ' . $orderBy;
- } else {
- $sql .= ' ORDER BY ' . $orderBy;
- }
- }
- }
+ if (($pagination = $this->getPagination()) !== false) {
+ $pagination->totalCount = $this->getTotalCount();
+ $sql .= ' ' . $qb->buildLimit($pagination->getLimit(), $pagination->getOffset());
+ }
- if (($pagination = $this->getPagination()) !== false) {
- $pagination->totalCount = $this->getTotalCount();
- $sql .= ' ' . $qb->buildLimit($pagination->getLimit(), $pagination->getOffset());
- }
+ return $this->db->createCommand($sql, $this->params)->queryAll();
+ }
- return $this->db->createCommand($sql, $this->params)->queryAll();
- }
+ /**
+ * @inheritdoc
+ */
+ protected function prepareKeys($models)
+ {
+ $keys = [];
+ if ($this->key !== null) {
+ foreach ($models as $model) {
+ if (is_string($this->key)) {
+ $keys[] = $model[$this->key];
+ } else {
+ $keys[] = call_user_func($this->key, $model);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function prepareKeys($models)
- {
- $keys = [];
- if ($this->key !== null) {
- foreach ($models as $model) {
- if (is_string($this->key)) {
- $keys[] = $model[$this->key];
- } else {
- $keys[] = call_user_func($this->key, $model);
- }
- }
- return $keys;
- } else {
- return array_keys($models);
- }
- }
+ return $keys;
+ } else {
+ return array_keys($models);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function prepareTotalCount()
- {
- return 0;
- }
+ /**
+ * @inheritdoc
+ */
+ protected function prepareTotalCount()
+ {
+ return 0;
+ }
}
diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php
index 55a9a802495..3c53e6d192e 100644
--- a/framework/db/ActiveQuery.php
+++ b/framework/db/ActiveQuery.php
@@ -70,544 +70,551 @@
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
- use ActiveQueryTrait;
- use ActiveRelationTrait;
-
- /**
- * @var string the SQL statement to be executed for retrieving AR records.
- * This is set by [[ActiveRecord::findBySql()]].
- */
- public $sql;
- /**
- * @var string|array the join condition to be used when this query is used in a relational context.
- * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
- * Otherwise, the condition will be used in the WHERE part of a query.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @see onCondition()
- */
- public $on;
- /**
- * @var array a list of relations that this query should be joined with
- */
- public $joinWith;
-
-
- /**
- * Executes query and returns all results as an array.
- * @param Connection $db the DB connection used to create the DB command.
- * If null, the DB connection returned by [[modelClass]] will be used.
- * @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- return parent::all($db);
- }
-
- /**
- * @inheritdoc
- */
- public function prepareResult($rows)
- {
- if (empty($rows)) {
- return [];
- }
-
- $models = $this->createModels($rows);
- if (!empty($this->join) && $this->indexBy === null) {
- $models = $this->removeDuplicatedModels($models);
- }
- if (!empty($this->with)) {
- $this->findWith($this->with, $models);
- }
- if (!$this->asArray) {
- foreach ($models as $model) {
- $model->afterFind();
- }
- }
- return $models;
- }
-
- /**
- * Removes duplicated models by checking their primary key values.
- * This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
- * @param array $models the models to be checked
- * @return array the distinctive models
- */
- private function removeDuplicatedModels($models)
- {
- $hash = [];
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $pks = $class::primaryKey();
-
- if (count($pks) > 1) {
- foreach ($models as $i => $model) {
- $key = [];
- foreach ($pks as $pk) {
- $key[] = $model[$pk];
- }
- $key = serialize($key);
- if (isset($hash[$key])) {
- unset($models[$i]);
- } else {
- $hash[$key] = true;
- }
- }
- } else {
- $pk = reset($pks);
- foreach ($models as $i => $model) {
- $key = $model[$pk];
- if (isset($hash[$key])) {
- unset($models[$i]);
- } else {
- $hash[$key] = true;
- }
- }
- }
-
- return array_values($models);
- }
-
- /**
- * Executes query and returns a single row of result.
- * @param Connection $db the DB connection used to create the DB command.
- * If null, the DB connection returned by [[modelClass]] will be used.
- * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
- * the query result may be either an array or an ActiveRecord object. Null will be returned
- * if the query results in nothing.
- */
- public function one($db = null)
- {
- $command = $this->createCommand($db);
- $row = $command->queryOne();
- if ($row !== false) {
- if ($this->asArray) {
- $model = $row;
- } else {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- $model = $class::instantiate($row);
- $class::populateRecord($model, $row);
- }
- if (!empty($this->with)) {
- $models = [$model];
- $this->findWith($this->with, $models);
- $model = $models[0];
- }
- if (!$this->asArray) {
- $model->afterFind();
- }
- return $model;
- } else {
- return null;
- }
- }
-
- /**
- * Creates a DB command that can be used to execute this query.
- * @param Connection $db the DB connection used to create the DB command.
- * If null, the DB connection returned by [[modelClass]] will be used.
- * @return Command the created DB command instance.
- */
- public function createCommand($db = null)
- {
- if ($this->primaryModel === null) {
- // not a relational context or eager loading
- if (!empty($this->on)) {
- $where = $this->where;
- $this->andWhere($this->on);
- $command = $this->createCommandInternal($db);
- $this->where = $where;
- return $command;
- } else {
- return $this->createCommandInternal($db);
- }
- } else {
- // lazy loading of a relation
- return $this->createRelationalCommand($db);
- }
- }
-
- /**
- * Creates a DB command that can be used to execute this query.
- * @param Connection $db the DB connection used to create the DB command.
- * If null, the DB connection returned by [[modelClass]] will be used.
- * @return Command the created DB command instance.
- */
- protected function createCommandInternal($db)
- {
- /** @var ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- if ($db === null) {
- $db = $modelClass::getDb();
- }
-
- 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;
- $params = $this->params;
- }
- return $db->createCommand($sql, $params);
- }
-
- /**
- * Creates a command for lazy loading of a relation.
- * @param Connection $db the DB connection used to create the DB command.
- * @return Command the created DB command instance.
- */
- private function createRelationalCommand($db = null)
- {
- $where = $this->where;
-
- if ($this->via instanceof self) {
- // via pivot table
- $viaModels = $this->via->findPivotRows([$this->primaryModel]);
- $this->filterByModels($viaModels);
- } elseif (is_array($this->via)) {
- // via relation
- /** @var ActiveQuery $viaQuery */
- list($viaName, $viaQuery) = $this->via;
- if ($viaQuery->multiple) {
- $viaModels = $viaQuery->all();
- $this->primaryModel->populateRelation($viaName, $viaModels);
- } else {
- $model = $viaQuery->one();
- $this->primaryModel->populateRelation($viaName, $model);
- $viaModels = $model === null ? [] : [$model];
- }
- $this->filterByModels($viaModels);
- } else {
- $this->filterByModels([$this->primaryModel]);
- }
-
- if (!empty($this->on)) {
- $this->andWhere($this->on);
- }
-
- $command = $this->createCommandInternal($db);
-
- $this->where = $where;
-
- return $command;
- }
-
- /**
- * Joins with the specified relations.
- *
- * This method allows you to reuse existing relation definitions to perform JOIN queries.
- * Based on the definition of the specified relation(s), the method will append one or multiple
- * JOIN statements to the current query.
- *
- * If the `$eagerLoading` parameter is true, the method will also eager loading the specified relations,
- * which is equivalent to calling [[with()]] using the specified relations.
- *
- * Note that because a JOIN query will be performed, you are responsible to disambiguate column names.
- *
- * This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement
- * for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.
- *
- * @param array $with the relations to be joined. Each array element represents a single relation.
- * The array keys are relation names, and the array values are the corresponding anonymous functions that
- * can be used to modify the relation queries on-the-fly. If a relation query does not need modification,
- * you may use the relation name as the array value. Sub-relations can also be specified (see [[with()]]).
- * For example,
- *
- * ```php
- * // find all orders that contain books, and eager loading "books"
- * Order::find()->joinWith('books', true, 'INNER JOIN')->all();
- * // find all orders, eager loading "books", and sort the orders and books by the book names.
- * Order::find()->joinWith([
- * 'books' => function ($query) {
- * $query->orderBy('tbl_item.name');
- * }
- * ])->all();
- * ```
- *
- * @param boolean|array $eagerLoading whether to eager load the relations specified in `$with`.
- * When this is a boolean, it applies to all relations specified in `$with`. Use an array
- * to explicitly list which relations in `$with` need to be eagerly loaded.
- * @param string|array $joinType the join type of the relations specified in `$with`.
- * When this is a string, it applies to all relations specified in `$with`. Use an array
- * in the format of `relationName => joinType` to specify different join types for different relations.
- * @return static the query object itself
- */
- public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
- {
- $this->joinWith[] = [(array)$with, $eagerLoading, $joinType];
- return $this;
- }
-
- private function buildJoinWith()
- {
- foreach ($this->joinWith as $config) {
- list ($with, $eagerLoading, $joinType) = $config;
- $this->joinWithRelations(new $this->modelClass, $with, $joinType);
-
- if (is_array($eagerLoading)) {
- foreach ($with as $name => $callback) {
- if (is_integer($name)) {
- if (!in_array($callback, $eagerLoading, true)) {
- unset($with[$name]);
- }
- } elseif (!in_array($name, $eagerLoading, true)) {
- unset($with[$name]);
- }
- }
- } elseif (!$eagerLoading) {
- $with = [];
- }
-
- $this->with($with);
- }
- }
-
- /**
- * Inner joins with the specified relations.
- * This is a shortcut method to [[joinWith()]] with the join type set as "INNER JOIN".
- * Please refer to [[joinWith()]] for detailed usage of this method.
- * @param array $with the relations to be joined with
- * @param boolean|array $eagerLoading whether to eager loading the relations
- * @return static the query object itself
- * @see joinWith()
- */
- public function innerJoinWith($with, $eagerLoading = true)
- {
- return $this->joinWith($with, $eagerLoading, 'INNER JOIN');
- }
-
- /**
- * Modifies the current query by adding join fragments based on the given relations.
- * @param ActiveRecord $model the primary model
- * @param array $with the relations to be joined
- * @param string|array $joinType the join type
- */
- private function joinWithRelations($model, $with, $joinType)
- {
- $relations = [];
-
- foreach ($with as $name => $callback) {
- if (is_integer($name)) {
- $name = $callback;
- $callback = null;
- }
-
- $primaryModel = $model;
- $parent = $this;
- $prefix = '';
- while (($pos = strpos($name, '.')) !== false) {
- $childName = substr($name, $pos + 1);
- $name = substr($name, 0, $pos);
- $fullName = $prefix === '' ? $name : "$prefix.$name";
- if (!isset($relations[$fullName])) {
- $relations[$fullName] = $relation = $primaryModel->getRelation($name);
- $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
- } else {
- $relation = $relations[$fullName];
- }
- $primaryModel = new $relation->modelClass;
- $parent = $relation;
- $prefix = $fullName;
- $name = $childName;
- }
-
- $fullName = $prefix === '' ? $name : "$prefix.$name";
- if (!isset($relations[$fullName])) {
- $relations[$fullName] = $relation = $primaryModel->getRelation($name);
- if ($callback !== null) {
- call_user_func($callback, $relation);
- }
- $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
- }
- }
- }
-
- /**
- * Returns the join type based on the given join type parameter and the relation name.
- * @param string|array $joinType the given join type(s)
- * @param string $name relation name
- * @return string the real join type
- */
- private function getJoinType($joinType, $name)
- {
- if (is_array($joinType) && isset($joinType[$name])) {
- return $joinType[$name];
- } else {
- return is_string($joinType) ? $joinType : 'INNER JOIN';
- }
- }
-
- /**
- * Returns the table name and the table alias for [[modelClass]].
- * @param ActiveQuery $query
- * @return array the table name and the table alias.
- */
- private function getQueryTableName($query)
- {
- if (empty($query->from)) {
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $tableName = $modelClass::tableName();
- } else {
- $tableName = '';
- foreach ($query->from as $alias => $tableName) {
- if (is_string($alias)) {
- return [$tableName, $alias];
- } else {
- break;
- }
- }
- }
-
- if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $tableName, $matches)) {
- $alias = $matches[2];
- } else {
- $alias = $tableName;
- }
-
- return [$tableName, $alias];
- }
-
- /**
- * Joins a parent query with a child query.
- * The current query object will be modified accordingly.
- * @param ActiveQuery $parent
- * @param ActiveQuery $child
- * @param string $joinType
- */
- private function joinWithRelation($parent, $child, $joinType)
- {
- $via = $child->via;
- $child->via = null;
- if ($via instanceof ActiveQuery) {
- // via table
- $this->joinWithRelation($parent, $via, $joinType);
- $this->joinWithRelation($via, $child, $joinType);
- return;
- } elseif (is_array($via)) {
- // via relation
- $this->joinWithRelation($parent, $via[1], $joinType);
- $this->joinWithRelation($via[1], $child, $joinType);
- return;
- }
-
- list ($parentTable, $parentAlias) = $this->getQueryTableName($parent);
- list ($childTable, $childAlias) = $this->getQueryTableName($child);
-
- if (!empty($child->link)) {
-
- if (strpos($parentAlias, '{{') === false) {
- $parentAlias = '{{' . $parentAlias . '}}';
- }
- if (strpos($childAlias, '{{') === false) {
- $childAlias = '{{' . $childAlias . '}}';
- }
-
- $on = [];
- foreach ($child->link as $childColumn => $parentColumn) {
- $on[] = "$parentAlias.[[$parentColumn]] = $childAlias.[[$childColumn]]";
- }
- $on = implode(' AND ', $on);
- if (!empty($child->on)) {
- $on = ['and', $on, $child->on];
- }
- } else {
- $on = $child->on;
- }
- $this->join($joinType, $childTable, $on);
-
-
- if (!empty($child->where)) {
- $this->andWhere($child->where);
- }
- if (!empty($child->having)) {
- $this->andHaving($child->having);
- }
- if (!empty($child->orderBy)) {
- $this->addOrderBy($child->orderBy);
- }
- if (!empty($child->groupBy)) {
- $this->addGroupBy($child->groupBy);
- }
- if (!empty($child->params)) {
- $this->addParams($child->params);
- }
- if (!empty($child->join)) {
- foreach ($child->join as $join) {
- $this->join[] = $join;
- }
- }
- if (!empty($child->union)) {
- foreach ($child->union as $union) {
- $this->union[] = $union;
- }
- }
- }
-
- /**
- * Sets the ON condition for a relational query.
- * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
- * Otherwise, the condition will be used in the WHERE part of a query.
- *
- * Use this method to specify additional conditions when declaring a relation in the [[ActiveRecord]] class:
- *
- * ```php
- * public function getActiveUsers()
- * {
- * return $this->hasMany(User::className(), ['id' => 'user_id'])->onCondition(['active' => true]);
- * }
- * ```
- *
- * @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- */
- public function onCondition($condition, $params = [])
- {
- $this->on = $condition;
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Specifies the pivot table for a relational query.
- *
- * Use this method to specify a pivot table when declaring a relation in the [[ActiveRecord]] class:
- *
- * ```php
- * public function getItems()
- * {
- * return $this->hasMany(Item::className(), ['id' => 'item_id'])
- * ->viaTable('tbl_order_item', ['order_id' => 'id']);
- * }
- * ```
- *
- * @param string $tableName the name of the pivot table.
- * @param array $link the link between the pivot table and the table associated with [[primaryModel]].
- * The keys of the array represent the columns in the pivot table, and the values represent the columns
- * in the [[primaryModel]] table.
- * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
- * Its signature should be `function($query)`, where `$query` is the query to be customized.
- * @return static
- * @see via()
- */
- public function viaTable($tableName, $link, $callable = null)
- {
- $relation = new ActiveQuery([
- 'modelClass' => get_class($this->primaryModel),
- 'from' => [$tableName],
- 'link' => $link,
- 'multiple' => true,
- 'asArray' => true,
- ]);
- $this->via = $relation;
- if ($callable !== null) {
- call_user_func($callable, $relation);
- }
- return $this;
- }
+ use ActiveQueryTrait;
+ use ActiveRelationTrait;
+
+ /**
+ * @var string the SQL statement to be executed for retrieving AR records.
+ * This is set by [[ActiveRecord::findBySql()]].
+ */
+ public $sql;
+ /**
+ * @var string|array the join condition to be used when this query is used in a relational context.
+ * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
+ * Otherwise, the condition will be used in the WHERE part of a query.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @see onCondition()
+ */
+ public $on;
+ /**
+ * @var array a list of relations that this query should be joined with
+ */
+ public $joinWith;
+
+ /**
+ * Executes query and returns all results as an array.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ return parent::all($db);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function prepareResult($rows)
+ {
+ if (empty($rows)) {
+ return [];
+ }
+
+ $models = $this->createModels($rows);
+ if (!empty($this->join) && $this->indexBy === null) {
+ $models = $this->removeDuplicatedModels($models);
+ }
+ if (!empty($this->with)) {
+ $this->findWith($this->with, $models);
+ }
+ if (!$this->asArray) {
+ foreach ($models as $model) {
+ $model->afterFind();
+ }
+ }
+
+ return $models;
+ }
+
+ /**
+ * Removes duplicated models by checking their primary key values.
+ * This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
+ * @param array $models the models to be checked
+ * @return array the distinctive models
+ */
+ private function removeDuplicatedModels($models)
+ {
+ $hash = [];
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $pks = $class::primaryKey();
+
+ if (count($pks) > 1) {
+ foreach ($models as $i => $model) {
+ $key = [];
+ foreach ($pks as $pk) {
+ $key[] = $model[$pk];
+ }
+ $key = serialize($key);
+ if (isset($hash[$key])) {
+ unset($models[$i]);
+ } else {
+ $hash[$key] = true;
+ }
+ }
+ } else {
+ $pk = reset($pks);
+ foreach ($models as $i => $model) {
+ $key = $model[$pk];
+ if (isset($hash[$key])) {
+ unset($models[$i]);
+ } else {
+ $hash[$key] = true;
+ }
+ }
+ }
+
+ return array_values($models);
+ }
+
+ /**
+ * Executes query and returns a single row of result.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
+ * the query result may be either an array or an ActiveRecord object. Null will be returned
+ * if the query results in nothing.
+ */
+ public function one($db = null)
+ {
+ $command = $this->createCommand($db);
+ $row = $command->queryOne();
+ if ($row !== false) {
+ if ($this->asArray) {
+ $model = $row;
+ } else {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ $model = $class::instantiate($row);
+ $class::populateRecord($model, $row);
+ }
+ if (!empty($this->with)) {
+ $models = [$model];
+ $this->findWith($this->with, $models);
+ $model = $models[0];
+ }
+ if (!$this->asArray) {
+ $model->afterFind();
+ }
+
+ return $model;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return Command the created DB command instance.
+ */
+ public function createCommand($db = null)
+ {
+ if ($this->primaryModel === null) {
+ // not a relational context or eager loading
+ if (!empty($this->on)) {
+ $where = $this->where;
+ $this->andWhere($this->on);
+ $command = $this->createCommandInternal($db);
+ $this->where = $where;
+
+ return $command;
+ } else {
+ return $this->createCommandInternal($db);
+ }
+ } else {
+ // lazy loading of a relation
+ return $this->createRelationalCommand($db);
+ }
+ }
+
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return Command the created DB command instance.
+ */
+ protected function createCommandInternal($db)
+ {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+ if ($db === null) {
+ $db = $modelClass::getDb();
+ }
+
+ 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;
+ $params = $this->params;
+ }
+
+ return $db->createCommand($sql, $params);
+ }
+
+ /**
+ * Creates a command for lazy loading of a relation.
+ * @param Connection $db the DB connection used to create the DB command.
+ * @return Command the created DB command instance.
+ */
+ private function createRelationalCommand($db = null)
+ {
+ $where = $this->where;
+
+ if ($this->via instanceof self) {
+ // via pivot table
+ $viaModels = $this->via->findPivotRows([$this->primaryModel]);
+ $this->filterByModels($viaModels);
+ } elseif (is_array($this->via)) {
+ // via relation
+ /** @var ActiveQuery $viaQuery */
+ list($viaName, $viaQuery) = $this->via;
+ if ($viaQuery->multiple) {
+ $viaModels = $viaQuery->all();
+ $this->primaryModel->populateRelation($viaName, $viaModels);
+ } else {
+ $model = $viaQuery->one();
+ $this->primaryModel->populateRelation($viaName, $model);
+ $viaModels = $model === null ? [] : [$model];
+ }
+ $this->filterByModels($viaModels);
+ } else {
+ $this->filterByModels([$this->primaryModel]);
+ }
+
+ if (!empty($this->on)) {
+ $this->andWhere($this->on);
+ }
+
+ $command = $this->createCommandInternal($db);
+
+ $this->where = $where;
+
+ return $command;
+ }
+
+ /**
+ * Joins with the specified relations.
+ *
+ * This method allows you to reuse existing relation definitions to perform JOIN queries.
+ * Based on the definition of the specified relation(s), the method will append one or multiple
+ * JOIN statements to the current query.
+ *
+ * If the `$eagerLoading` parameter is true, the method will also eager loading the specified relations,
+ * which is equivalent to calling [[with()]] using the specified relations.
+ *
+ * Note that because a JOIN query will be performed, you are responsible to disambiguate column names.
+ *
+ * This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement
+ * for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.
+ *
+ * @param array $with the relations to be joined. Each array element represents a single relation.
+ * The array keys are relation names, and the array values are the corresponding anonymous functions that
+ * can be used to modify the relation queries on-the-fly. If a relation query does not need modification,
+ * you may use the relation name as the array value. Sub-relations can also be specified (see [[with()]]).
+ * For example,
+ *
+ * ```php
+ * // find all orders that contain books, and eager loading "books"
+ * Order::find()->joinWith('books', true, 'INNER JOIN')->all();
+ * // find all orders, eager loading "books", and sort the orders and books by the book names.
+ * Order::find()->joinWith([
+ * 'books' => function ($query) {
+ * $query->orderBy('tbl_item.name');
+ * }
+ * ])->all();
+ * ```
+ *
+ * @param boolean|array $eagerLoading whether to eager load the relations specified in `$with`.
+ * When this is a boolean, it applies to all relations specified in `$with`. Use an array
+ * to explicitly list which relations in `$with` need to be eagerly loaded.
+ * @param string|array $joinType the join type of the relations specified in `$with`.
+ * When this is a string, it applies to all relations specified in `$with`. Use an array
+ * in the format of `relationName => joinType` to specify different join types for different relations.
+ * @return static the query object itself
+ */
+ public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
+ {
+ $this->joinWith[] = [(array) $with, $eagerLoading, $joinType];
+
+ return $this;
+ }
+
+ private function buildJoinWith()
+ {
+ foreach ($this->joinWith as $config) {
+ list ($with, $eagerLoading, $joinType) = $config;
+ $this->joinWithRelations(new $this->modelClass, $with, $joinType);
+
+ if (is_array($eagerLoading)) {
+ foreach ($with as $name => $callback) {
+ if (is_integer($name)) {
+ if (!in_array($callback, $eagerLoading, true)) {
+ unset($with[$name]);
+ }
+ } elseif (!in_array($name, $eagerLoading, true)) {
+ unset($with[$name]);
+ }
+ }
+ } elseif (!$eagerLoading) {
+ $with = [];
+ }
+
+ $this->with($with);
+ }
+ }
+
+ /**
+ * Inner joins with the specified relations.
+ * This is a shortcut method to [[joinWith()]] with the join type set as "INNER JOIN".
+ * Please refer to [[joinWith()]] for detailed usage of this method.
+ * @param array $with the relations to be joined with
+ * @param boolean|array $eagerLoading whether to eager loading the relations
+ * @return static the query object itself
+ * @see joinWith()
+ */
+ public function innerJoinWith($with, $eagerLoading = true)
+ {
+ return $this->joinWith($with, $eagerLoading, 'INNER JOIN');
+ }
+
+ /**
+ * Modifies the current query by adding join fragments based on the given relations.
+ * @param ActiveRecord $model the primary model
+ * @param array $with the relations to be joined
+ * @param string|array $joinType the join type
+ */
+ private function joinWithRelations($model, $with, $joinType)
+ {
+ $relations = [];
+
+ foreach ($with as $name => $callback) {
+ if (is_integer($name)) {
+ $name = $callback;
+ $callback = null;
+ }
+
+ $primaryModel = $model;
+ $parent = $this;
+ $prefix = '';
+ while (($pos = strpos($name, '.')) !== false) {
+ $childName = substr($name, $pos + 1);
+ $name = substr($name, 0, $pos);
+ $fullName = $prefix === '' ? $name : "$prefix.$name";
+ if (!isset($relations[$fullName])) {
+ $relations[$fullName] = $relation = $primaryModel->getRelation($name);
+ $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
+ } else {
+ $relation = $relations[$fullName];
+ }
+ $primaryModel = new $relation->modelClass;
+ $parent = $relation;
+ $prefix = $fullName;
+ $name = $childName;
+ }
+
+ $fullName = $prefix === '' ? $name : "$prefix.$name";
+ if (!isset($relations[$fullName])) {
+ $relations[$fullName] = $relation = $primaryModel->getRelation($name);
+ if ($callback !== null) {
+ call_user_func($callback, $relation);
+ }
+ $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
+ }
+ }
+ }
+
+ /**
+ * Returns the join type based on the given join type parameter and the relation name.
+ * @param string|array $joinType the given join type(s)
+ * @param string $name relation name
+ * @return string the real join type
+ */
+ private function getJoinType($joinType, $name)
+ {
+ if (is_array($joinType) && isset($joinType[$name])) {
+ return $joinType[$name];
+ } else {
+ return is_string($joinType) ? $joinType : 'INNER JOIN';
+ }
+ }
+
+ /**
+ * Returns the table name and the table alias for [[modelClass]].
+ * @param ActiveQuery $query
+ * @return array the table name and the table alias.
+ */
+ private function getQueryTableName($query)
+ {
+ if (empty($query->from)) {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $tableName = $modelClass::tableName();
+ } else {
+ $tableName = '';
+ foreach ($query->from as $alias => $tableName) {
+ if (is_string($alias)) {
+ return [$tableName, $alias];
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $tableName, $matches)) {
+ $alias = $matches[2];
+ } else {
+ $alias = $tableName;
+ }
+
+ return [$tableName, $alias];
+ }
+
+ /**
+ * Joins a parent query with a child query.
+ * The current query object will be modified accordingly.
+ * @param ActiveQuery $parent
+ * @param ActiveQuery $child
+ * @param string $joinType
+ */
+ private function joinWithRelation($parent, $child, $joinType)
+ {
+ $via = $child->via;
+ $child->via = null;
+ if ($via instanceof ActiveQuery) {
+ // via table
+ $this->joinWithRelation($parent, $via, $joinType);
+ $this->joinWithRelation($via, $child, $joinType);
+
+ return;
+ } elseif (is_array($via)) {
+ // via relation
+ $this->joinWithRelation($parent, $via[1], $joinType);
+ $this->joinWithRelation($via[1], $child, $joinType);
+
+ return;
+ }
+
+ list ($parentTable, $parentAlias) = $this->getQueryTableName($parent);
+ list ($childTable, $childAlias) = $this->getQueryTableName($child);
+
+ if (!empty($child->link)) {
+
+ if (strpos($parentAlias, '{{') === false) {
+ $parentAlias = '{{' . $parentAlias . '}}';
+ }
+ if (strpos($childAlias, '{{') === false) {
+ $childAlias = '{{' . $childAlias . '}}';
+ }
+
+ $on = [];
+ foreach ($child->link as $childColumn => $parentColumn) {
+ $on[] = "$parentAlias.[[$parentColumn]] = $childAlias.[[$childColumn]]";
+ }
+ $on = implode(' AND ', $on);
+ if (!empty($child->on)) {
+ $on = ['and', $on, $child->on];
+ }
+ } else {
+ $on = $child->on;
+ }
+ $this->join($joinType, $childTable, $on);
+
+ if (!empty($child->where)) {
+ $this->andWhere($child->where);
+ }
+ if (!empty($child->having)) {
+ $this->andHaving($child->having);
+ }
+ if (!empty($child->orderBy)) {
+ $this->addOrderBy($child->orderBy);
+ }
+ if (!empty($child->groupBy)) {
+ $this->addGroupBy($child->groupBy);
+ }
+ if (!empty($child->params)) {
+ $this->addParams($child->params);
+ }
+ if (!empty($child->join)) {
+ foreach ($child->join as $join) {
+ $this->join[] = $join;
+ }
+ }
+ if (!empty($child->union)) {
+ foreach ($child->union as $union) {
+ $this->union[] = $union;
+ }
+ }
+ }
+
+ /**
+ * Sets the ON condition for a relational query.
+ * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
+ * Otherwise, the condition will be used in the WHERE part of a query.
+ *
+ * Use this method to specify additional conditions when declaring a relation in the [[ActiveRecord]] class:
+ *
+ * ```php
+ * public function getActiveUsers()
+ * {
+ * return $this->hasMany(User::className(), ['id' => 'user_id'])->onCondition(['active' => true]);
+ * }
+ * ```
+ *
+ * @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ */
+ public function onCondition($condition, $params = [])
+ {
+ $this->on = $condition;
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Specifies the pivot table for a relational query.
+ *
+ * Use this method to specify a pivot table when declaring a relation in the [[ActiveRecord]] class:
+ *
+ * ```php
+ * public function getItems()
+ * {
+ * return $this->hasMany(Item::className(), ['id' => 'item_id'])
+ * ->viaTable('tbl_order_item', ['order_id' => 'id']);
+ * }
+ * ```
+ *
+ * @param string $tableName the name of the pivot table.
+ * @param array $link the link between the pivot table and the table associated with [[primaryModel]].
+ * The keys of the array represent the columns in the pivot table, and the values represent the columns
+ * in the [[primaryModel]] table.
+ * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
+ * Its signature should be `function($query)`, where `$query` is the query to be customized.
+ * @return static
+ * @see via()
+ */
+ public function viaTable($tableName, $link, $callable = null)
+ {
+ $relation = new ActiveQuery([
+ 'modelClass' => get_class($this->primaryModel),
+ 'from' => [$tableName],
+ 'link' => $link,
+ 'multiple' => true,
+ 'asArray' => true,
+ ]);
+ $this->via = $relation;
+ if ($callable !== null) {
+ call_user_func($callable, $relation);
+ }
+
+ return $this;
+ }
}
diff --git a/framework/db/ActiveQueryInterface.php b/framework/db/ActiveQueryInterface.php
index 332d576fe74..7d3e7f19bf2 100644
--- a/framework/db/ActiveQueryInterface.php
+++ b/framework/db/ActiveQueryInterface.php
@@ -22,78 +22,78 @@
*/
interface ActiveQueryInterface extends QueryInterface
{
- /**
- * Sets the [[asArray]] property.
- * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
- * @return static the query object itself
- */
- public function asArray($value = true);
+ /**
+ * Sets the [[asArray]] property.
+ * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
+ * @return static the query object itself
+ */
+ public function asArray($value = true);
- /**
- * Sets the [[indexBy]] property.
- * @param string|callable $column the name of the column by which the query results should be indexed by.
- * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
- * row or model data. The signature of the callable should be:
- *
- * ~~~
- * // $model is an AR instance when `asArray` is false,
- * // or an array of column values when `asArray` is true.
- * function ($model)
- * {
- * // return the index value corresponding to $model
- * }
- * ~~~
- *
- * @return static the query object itself
- */
- public function indexBy($column);
+ /**
+ * Sets the [[indexBy]] property.
+ * @param string|callable $column the name of the column by which the query results should be indexed by.
+ * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
+ * row or model data. The signature of the callable should be:
+ *
+ * ~~~
+ * // $model is an AR instance when `asArray` is false,
+ * // or an array of column values when `asArray` is true.
+ * function ($model)
+ * {
+ * // return the index value corresponding to $model
+ * }
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function indexBy($column);
- /**
- * Specifies the relations with which this query should be performed.
- *
- * The parameters to this method can be either one or multiple strings, or a single array
- * of relation names and the optional callbacks to customize the relations.
- *
- * A relation name can refer to a relation defined in [[ActiveQueryTrait::modelClass|modelClass]]
- * or a sub-relation that stands for a relation of a related record.
- * For example, `orders.address` means the `address` relation defined
- * in the model class corresponding to the `orders` relation.
- *
- * The followings are some usage examples:
- *
- * ~~~
- * // find customers together with their orders and country
- * Customer::find()->with('orders', 'country')->all();
- * // find customers together with their orders and the orders' shipping address
- * Customer::find()->with('orders.address')->all();
- * // find customers together with their country and orders of status 1
- * Customer::find()->with([
- * 'orders' => function($query) {
- * $query->andWhere('status = 1');
- * },
- * 'country',
- * ])->all();
- * ~~~
- *
- * @return static the query object itself
- */
- public function with();
+ /**
+ * Specifies the relations with which this query should be performed.
+ *
+ * The parameters to this method can be either one or multiple strings, or a single array
+ * of relation names and the optional callbacks to customize the relations.
+ *
+ * A relation name can refer to a relation defined in [[ActiveQueryTrait::modelClass|modelClass]]
+ * or a sub-relation that stands for a relation of a related record.
+ * For example, `orders.address` means the `address` relation defined
+ * in the model class corresponding to the `orders` relation.
+ *
+ * The followings are some usage examples:
+ *
+ * ~~~
+ * // find customers together with their orders and country
+ * Customer::find()->with('orders', 'country')->all();
+ * // find customers together with their orders and the orders' shipping address
+ * Customer::find()->with('orders.address')->all();
+ * // find customers together with their country and orders of status 1
+ * Customer::find()->with([
+ * 'orders' => function ($query) {
+ * $query->andWhere('status = 1');
+ * },
+ * 'country',
+ * ])->all();
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function with();
- /**
- * Specifies the relation associated with the pivot table for use in relational query.
- * @param string $relationName the relation name. This refers to a relation declared in the [[ActiveRelationTrait::primaryModel|primaryModel]] of the relation.
- * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
- * Its signature should be `function($query)`, where `$query` is the query to be customized.
- * @return static the relation object itself.
- */
- public function via($relationName, $callable = null);
+ /**
+ * Specifies the relation associated with the pivot table for use in relational query.
+ * @param string $relationName the relation name. This refers to a relation declared in the [[ActiveRelationTrait::primaryModel|primaryModel]] of the relation.
+ * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
+ * Its signature should be `function($query)`, where `$query` is the query to be customized.
+ * @return static the relation object itself.
+ */
+ public function via($relationName, $callable = null);
- /**
- * Finds the related records for the specified primary record.
- * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
- * @param string $name the relation name
- * @param ActiveRecordInterface $model the primary model
- * @return mixed the related record(s)
- */
- public function findFor($name, $model);
+ /**
+ * Finds the related records for the specified primary record.
+ * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
+ * @param string $name the relation name
+ * @param ActiveRecordInterface $model the primary model
+ * @return mixed the related record(s)
+ */
+ public function findFor($name, $model);
}
diff --git a/framework/db/ActiveQueryTrait.php b/framework/db/ActiveQueryTrait.php
index fb2f9f98e75..dd3a4543611 100644
--- a/framework/db/ActiveQueryTrait.php
+++ b/framework/db/ActiveQueryTrait.php
@@ -16,191 +16,194 @@
*/
trait ActiveQueryTrait
{
- /**
- * @var string the name of the ActiveRecord class.
- */
- public $modelClass;
- /**
- * @var array a list of relations that this query should be performed with
- */
- public $with;
- /**
- * @var boolean whether to return each record as an array. If false (default), an object
- * of [[modelClass]] will be created to represent each record.
- */
- public $asArray;
+ /**
+ * @var string the name of the ActiveRecord class.
+ */
+ public $modelClass;
+ /**
+ * @var array a list of relations that this query should be performed with
+ */
+ public $with;
+ /**
+ * @var boolean whether to return each record as an array. If false (default), an object
+ * of [[modelClass]] will be created to represent each record.
+ */
+ public $asArray;
- /**
- * Sets the [[asArray]] property.
- * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
- * @return static the query object itself
- */
- public function asArray($value = true)
- {
- $this->asArray = $value;
- return $this;
- }
+ /**
+ * Sets the [[asArray]] property.
+ * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
+ * @return static the query object itself
+ */
+ public function asArray($value = true)
+ {
+ $this->asArray = $value;
- /**
- * Specifies the relations with which this query should be performed.
- *
- * The parameters to this method can be either one or multiple strings, or a single array
- * of relation names and the optional callbacks to customize the relations.
- *
- * A relation name can refer to a relation defined in [[modelClass]]
- * or a sub-relation that stands for a relation of a related record.
- * For example, `orders.address` means the `address` relation defined
- * in the model class corresponding to the `orders` relation.
- *
- * The followings are some usage examples:
- *
- * ~~~
- * // find customers together with their orders and country
- * Customer::find()->with('orders', 'country')->all();
- * // find customers together with their orders and the orders' shipping address
- * Customer::find()->with('orders.address')->all();
- * // find customers together with their country and orders of status 1
- * Customer::find()->with([
- * 'orders' => function($query) {
- * $query->andWhere('status = 1');
- * },
- * 'country',
- * ])->all();
- * ~~~
- *
- * You can call `with()` multiple times. Each call will add relations to the existing ones.
- * For example, the following two statements are equivalent:
- *
- * ~~~
- * Customer::find()->with('orders', 'country')->all();
- * Customer::find()->with('orders')->with('country')->all();
- * ~~~
- *
- * @return static the query object itself
- */
- public function with()
- {
- $with = func_get_args();
- if (isset($with[0]) && is_array($with[0])) {
- // the parameter is given as an array
- $with = $with[0];
- }
+ return $this;
+ }
- if (empty($this->with)) {
- $this->with = $with;
- } elseif (!empty($with)) {
- foreach ($with as $name => $value) {
- if (is_integer($name)) {
- // repeating relation is fine as normalizeRelations() handle it well
- $this->with[] = $value;
- } else {
- $this->with[$name] = $value;
- }
- }
- }
+ /**
+ * Specifies the relations with which this query should be performed.
+ *
+ * The parameters to this method can be either one or multiple strings, or a single array
+ * of relation names and the optional callbacks to customize the relations.
+ *
+ * A relation name can refer to a relation defined in [[modelClass]]
+ * or a sub-relation that stands for a relation of a related record.
+ * For example, `orders.address` means the `address` relation defined
+ * in the model class corresponding to the `orders` relation.
+ *
+ * The followings are some usage examples:
+ *
+ * ~~~
+ * // find customers together with their orders and country
+ * Customer::find()->with('orders', 'country')->all();
+ * // find customers together with their orders and the orders' shipping address
+ * Customer::find()->with('orders.address')->all();
+ * // find customers together with their country and orders of status 1
+ * Customer::find()->with([
+ * 'orders' => function ($query) {
+ * $query->andWhere('status = 1');
+ * },
+ * 'country',
+ * ])->all();
+ * ~~~
+ *
+ * You can call `with()` multiple times. Each call will add relations to the existing ones.
+ * For example, the following two statements are equivalent:
+ *
+ * ~~~
+ * Customer::find()->with('orders', 'country')->all();
+ * Customer::find()->with('orders')->with('country')->all();
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function with()
+ {
+ $with = func_get_args();
+ if (isset($with[0]) && is_array($with[0])) {
+ // the parameter is given as an array
+ $with = $with[0];
+ }
- return $this;
- }
+ if (empty($this->with)) {
+ $this->with = $with;
+ } elseif (!empty($with)) {
+ foreach ($with as $name => $value) {
+ if (is_integer($name)) {
+ // repeating relation is fine as normalizeRelations() handle it well
+ $this->with[] = $value;
+ } else {
+ $this->with[$name] = $value;
+ }
+ }
+ }
- /**
- * Converts found rows into model instances
- * @param array $rows
- * @return array|ActiveRecord[]
- */
- private function createModels($rows)
- {
- $models = [];
- if ($this->asArray) {
- if ($this->indexBy === null) {
- return $rows;
- }
- foreach ($rows as $row) {
- if (is_string($this->indexBy)) {
- $key = $row[$this->indexBy];
- } else {
- $key = call_user_func($this->indexBy, $row);
- }
- $models[$key] = $row;
- }
- } else {
- /** @var ActiveRecord $class */
- $class = $this->modelClass;
- if ($this->indexBy === null) {
- foreach ($rows as $row) {
- $model = $class::instantiate($row);
- $class::populateRecord($model, $row);
- $models[] = $model;
- }
- } else {
- foreach ($rows as $row) {
- $model = $class::instantiate($row);
- $class::populateRecord($model, $row);
- if (is_string($this->indexBy)) {
- $key = $model->{$this->indexBy};
- } else {
- $key = call_user_func($this->indexBy, $model);
- }
- $models[$key] = $model;
- }
- }
- }
- return $models;
- }
+ return $this;
+ }
- /**
- * Finds records corresponding to one or multiple relations and populates them into the primary models.
- * @param array $with a list of relations that this query should be performed with. Please
- * refer to [[with()]] for details about specifying this parameter.
- * @param array|ActiveRecord[] $models the primary models (can be either AR instances or arrays)
- */
- public function findWith($with, &$models)
- {
- $primaryModel = new $this->modelClass;
- $relations = $this->normalizeRelations($primaryModel, $with);
- foreach ($relations as $name => $relation) {
- if ($relation->asArray === null) {
- // inherit asArray from primary query
- $relation->asArray = $this->asArray;
- }
- $relation->populateRelation($name, $models);
- }
- }
+ /**
+ * Converts found rows into model instances
+ * @param array $rows
+ * @return array|ActiveRecord[]
+ */
+ private function createModels($rows)
+ {
+ $models = [];
+ if ($this->asArray) {
+ if ($this->indexBy === null) {
+ return $rows;
+ }
+ foreach ($rows as $row) {
+ if (is_string($this->indexBy)) {
+ $key = $row[$this->indexBy];
+ } else {
+ $key = call_user_func($this->indexBy, $row);
+ }
+ $models[$key] = $row;
+ }
+ } else {
+ /** @var ActiveRecord $class */
+ $class = $this->modelClass;
+ if ($this->indexBy === null) {
+ foreach ($rows as $row) {
+ $model = $class::instantiate($row);
+ $class::populateRecord($model, $row);
+ $models[] = $model;
+ }
+ } else {
+ foreach ($rows as $row) {
+ $model = $class::instantiate($row);
+ $class::populateRecord($model, $row);
+ if (is_string($this->indexBy)) {
+ $key = $model->{$this->indexBy};
+ } else {
+ $key = call_user_func($this->indexBy, $model);
+ }
+ $models[$key] = $model;
+ }
+ }
+ }
- /**
- * @param ActiveRecord $model
- * @param array $with
- * @return ActiveQueryInterface[]
- */
- private function normalizeRelations($model, $with)
- {
- $relations = [];
- foreach ($with as $name => $callback) {
- if (is_integer($name)) {
- $name = $callback;
- $callback = null;
- }
- if (($pos = strpos($name, '.')) !== false) {
- // with sub-relations
- $childName = substr($name, $pos + 1);
- $name = substr($name, 0, $pos);
- } else {
- $childName = null;
- }
+ return $models;
+ }
- if (!isset($relations[$name])) {
- $relation = $model->getRelation($name);
- $relation->primaryModel = null;
- $relations[$name] = $relation;
- } else {
- $relation = $relations[$name];
- }
+ /**
+ * Finds records corresponding to one or multiple relations and populates them into the primary models.
+ * @param array $with a list of relations that this query should be performed with. Please
+ * refer to [[with()]] for details about specifying this parameter.
+ * @param array|ActiveRecord[] $models the primary models (can be either AR instances or arrays)
+ */
+ public function findWith($with, &$models)
+ {
+ $primaryModel = new $this->modelClass;
+ $relations = $this->normalizeRelations($primaryModel, $with);
+ foreach ($relations as $name => $relation) {
+ if ($relation->asArray === null) {
+ // inherit asArray from primary query
+ $relation->asArray = $this->asArray;
+ }
+ $relation->populateRelation($name, $models);
+ }
+ }
- if (isset($childName)) {
- $relation->with[$childName] = $callback;
- } elseif ($callback !== null) {
- call_user_func($callback, $relation);
- }
- }
- return $relations;
- }
+ /**
+ * @param ActiveRecord $model
+ * @param array $with
+ * @return ActiveQueryInterface[]
+ */
+ private function normalizeRelations($model, $with)
+ {
+ $relations = [];
+ foreach ($with as $name => $callback) {
+ if (is_integer($name)) {
+ $name = $callback;
+ $callback = null;
+ }
+ if (($pos = strpos($name, '.')) !== false) {
+ // with sub-relations
+ $childName = substr($name, $pos + 1);
+ $name = substr($name, 0, $pos);
+ } else {
+ $childName = null;
+ }
+
+ if (!isset($relations[$name])) {
+ $relation = $model->getRelation($name);
+ $relation->primaryModel = null;
+ $relations[$name] = $relation;
+ } else {
+ $relation = $relations[$name];
+ }
+
+ if (isset($childName)) {
+ $relation->with[$childName] = $callback;
+ } elseif ($callback !== null) {
+ call_user_func($callback, $relation);
+ }
+ }
+
+ return $relations;
+ }
}
diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php
index 539508ba868..74b22f89be5 100644
--- a/framework/db/ActiveRecord.php
+++ b/framework/db/ActiveRecord.php
@@ -74,533 +74,544 @@
*/
class ActiveRecord extends BaseActiveRecord
{
- /**
- * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
- */
- const OP_INSERT = 0x01;
- /**
- * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
- */
- const OP_UPDATE = 0x02;
- /**
- * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
- */
- const OP_DELETE = 0x04;
- /**
- * All three operations: insert, update, delete.
- * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
- */
- const OP_ALL = 0x07;
-
- /**
- * Returns the database connection used by this AR class.
- * By default, the "db" application component is used as the database connection.
- * You may override this method if you want to use a different database connection.
- * @return Connection the database connection used by this AR class.
- */
- public static function getDb()
- {
- return \Yii::$app->getDb();
- }
-
- /**
- * Creates an [[ActiveQuery]] instance with a given SQL statement.
- *
- * Note that because the SQL statement is already specified, calling additional
- * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]]
- * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is
- * still fine.
- *
- * Below is an example:
- *
- * ~~~
- * $customers = Customer::findBySql('SELECT * FROM tbl_customer')->all();
- * ~~~
- *
- * @param string $sql the SQL statement to be executed
- * @param array $params parameters to be bound to the SQL statement during execution.
- * @return ActiveQuery the newly created [[ActiveQuery]] instance
- */
- public static function findBySql($sql, $params = [])
- {
- $query = static::createQuery();
- $query->sql = $sql;
- return $query->params($params);
- }
-
- /**
- * Updates the whole table using the provided attribute values and conditions.
- * For example, to change the status to be 1 for all customers whose status is 2:
- *
- * ~~~
- * Customer::updateAll(['status' => 1], 'status = 2');
- * ~~~
- *
- * @param array $attributes attribute values (name-value pairs) to be saved into the table
- * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return integer the number of rows updated
- */
- public static function updateAll($attributes, $condition = '', $params = [])
- {
- $command = static::getDb()->createCommand();
- $command->update(static::tableName(), $attributes, $condition, $params);
- return $command->execute();
- }
-
- /**
- * Updates the whole table using the provided counter changes and conditions.
- * For example, to increment all customers' age by 1,
- *
- * ~~~
- * Customer::updateAllCounters(['age' => 1]);
- * ~~~
- *
- * @param array $counters the counters to be updated (attribute name => increment value).
- * Use negative values if you want to decrement the counters.
- * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method.
- * @return integer the number of rows updated
- */
- public static function updateAllCounters($counters, $condition = '', $params = [])
- {
- $n = 0;
- foreach ($counters as $name => $value) {
- $counters[$name] = new Expression("[[$name]]+:bp{$n}", [":bp{$n}" => $value]);
- $n++;
- }
- $command = static::getDb()->createCommand();
- $command->update(static::tableName(), $counters, $condition, $params);
- return $command->execute();
- }
-
- /**
- * Deletes rows in the table using the provided conditions.
- * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
- *
- * For example, to delete all customers whose status is 3:
- *
- * ~~~
- * Customer::deleteAll('status = 3');
- * ~~~
- *
- * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return integer the number of rows deleted
- */
- public static function deleteAll($condition = '', $params = [])
- {
- $command = static::getDb()->createCommand();
- $command->delete(static::tableName(), $condition, $params);
- return $command->execute();
- }
-
- /**
- * Creates an [[ActiveQuery]] instance.
- *
- * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
- * by [[hasOne()]] and [[hasMany()]] to create a relational query.
- * You may override this method to return a customized query (e.g. `CustomerQuery` specified
- * written for querying `Customer` purpose.)
- *
- * You may also define default conditions that should apply to all queries unless overridden:
- *
- * ```php
- * public static function createQuery($config = [])
- * {
- * return parent::createQuery($config)->where(['deleted' => false]);
- * }
- * ```
- *
- * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
- * default condition. Using [[Query::where()]] will override the default condition.
- *
- * @param array $config the configuration passed to the ActiveQuery class.
- * @return ActiveQuery the newly created [[ActiveQuery]] instance.
- */
- public static function createQuery($config = [])
- {
- $config['modelClass'] = get_called_class();
- return new ActiveQuery($config);
- }
-
- /**
- * Declares the name of the database table associated with this AR class.
- * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]
- * with prefix [[Connection::tablePrefix]]. For example if [[Connection::tablePrefix]] is 'tbl_',
- * 'Customer' becomes 'tbl_customer', and 'OrderItem' becomes 'tbl_order_item'. You may override this method
- * if the table is not named after this convention.
- * @return string the table name
- */
- public static function tableName()
- {
- return '{{%' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_') . '}}';
- }
-
- /**
- * Returns the schema information of the DB table associated with this AR class.
- * @return TableSchema the schema information of the DB table associated with this AR class.
- * @throws InvalidConfigException if the table for the AR class does not exist.
- */
- public static function getTableSchema()
- {
- $schema = static::getDb()->getTableSchema(static::tableName());
- if ($schema !== null) {
- return $schema;
- } else {
- throw new InvalidConfigException("The table does not exist: " . static::tableName());
- }
- }
-
- /**
- * Returns the primary key name(s) for this AR class.
- * The default implementation will return the primary key(s) as declared
- * in the DB table that is associated with this AR class.
- *
- * If the DB table does not declare any primary key, you should override
- * this method to return the attributes that you want to use as primary keys
- * for this AR class.
- *
- * Note that an array should be returned even for a table with single primary key.
- *
- * @return string[] the primary keys of the associated database table.
- */
- public static function primaryKey()
- {
- return static::getTableSchema()->primaryKey;
- }
-
- /**
- * Returns the list of all attribute names of the model.
- * The default implementation will return all column names of the table associated with this AR class.
- * @return array list of attribute names.
- */
- public function attributes()
- {
- return array_keys(static::getTableSchema()->columns);
- }
-
- /**
- * Declares which DB operations should be performed within a transaction in different scenarios.
- * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
- * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
- * By default, these methods are NOT enclosed in a DB transaction.
- *
- * In some scenarios, to ensure data consistency, you may want to enclose some or all of them
- * in transactions. You can do so by overriding this method and returning the operations
- * that need to be transactional. For example,
- *
- * ~~~
- * return [
- * 'admin' => self::OP_INSERT,
- * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
- * // the above is equivalent to the following:
- * // 'api' => self::OP_ALL,
- *
- * ];
- * ~~~
- *
- * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
- * should be done in a transaction; and in the "api" scenario, all the operations should be done
- * in a transaction.
- *
- * @return array the declarations of transactional operations. The array keys are scenarios names,
- * and the array values are the corresponding transaction operations.
- */
- public function transactions()
- {
- return [];
- }
-
- /**
- * @inheritdoc
- */
- public static function populateRecord($record, $row)
- {
- $columns = static::getTableSchema()->columns;
- foreach ($row as $name => $value) {
- if (isset($columns[$name])) {
- $row[$name] = $columns[$name]->typecast($value);
- }
- }
- parent::populateRecord($record, $row);
- }
-
- /**
- * Inserts a row into the associated database table using the attribute values of this record.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
- * fails, it will skip the rest of the steps;
- * 2. call [[afterValidate()]] when `$runValidation` is true.
- * 3. call [[beforeSave()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 4. insert the record into database. If this fails, it will skip the rest of the steps;
- * 5. call [[afterSave()]];
- *
- * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
- * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
- * will be raised by the corresponding methods.
- *
- * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database.
- *
- * If the table's primary key is auto-incremental and is null during insertion,
- * it will be populated with the actual value after insertion.
- *
- * For example, to insert a customer record:
- *
- * ~~~
- * $customer = new Customer;
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->insert();
- * ~~~
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted into the database.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return boolean whether the attributes are valid and the record is inserted successfully.
- * @throws \Exception in case insert failed.
- */
- public function insert($runValidation = true, $attributes = null)
- {
- if ($runValidation && !$this->validate($attributes)) {
- return false;
- }
- $db = static::getDb();
- if ($this->isTransactional(self::OP_INSERT)) {
- $transaction = $db->beginTransaction();
- try {
- $result = $this->insertInternal($attributes);
- if ($result === false) {
- $transaction->rollBack();
- } else {
- $transaction->commit();
- }
- } catch (\Exception $e) {
- $transaction->rollBack();
- throw $e;
- }
- } else {
- $result = $this->insertInternal($attributes);
- }
- return $result;
- }
-
- /**
- * Inserts an ActiveRecord into DB without considering transaction.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return boolean whether the record is inserted successfully.
- */
- protected function insertInternal($attributes = null)
- {
- if (!$this->beforeSave(true)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- foreach ($this->getPrimaryKey(true) as $key => $value) {
- $values[$key] = $value;
- }
- }
- $db = static::getDb();
- $command = $db->createCommand()->insert($this->tableName(), $values);
- if (!$command->execute()) {
- return false;
- }
- $table = $this->getTableSchema();
- if ($table->sequenceName !== null) {
- foreach ($table->primaryKey as $name) {
- if ($this->getAttribute($name) === null) {
- $id = $db->getLastInsertID($table->sequenceName);
- $this->setAttribute($name, $id);
- $this->setOldAttribute($name, $id);
- break;
- }
- }
- }
- foreach ($values as $name => $value) {
- $this->setOldAttribute($name, $value);
- }
- $this->afterSave(true);
- return true;
- }
-
- /**
- * Saves the changes to this active record into the associated database table.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
- * fails, it will skip the rest of the steps;
- * 2. call [[afterValidate()]] when `$runValidation` is true.
- * 3. call [[beforeSave()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 4. save the record into database. If this fails, it will skip the rest of the steps;
- * 5. call [[afterSave()]];
- *
- * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
- * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
- * will be raised by the corresponding methods.
- *
- * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
- *
- * For example, to update a customer record:
- *
- * ~~~
- * $customer = Customer::find($id);
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->update();
- * ~~~
- *
- * Note that it is possible the update does not affect any row in the table.
- * In this case, this method will return 0. For this reason, you should use the following
- * code to check if update() is successful or not:
- *
- * ~~~
- * if ($this->update() !== false) {
- * // update successful
- * } else {
- * // update failed
- * }
- * ~~~
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted into the database.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return integer|boolean the number of rows affected, or false if validation fails
- * or [[beforeSave()]] stops the updating process.
- * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
- * being updated is outdated.
- * @throws \Exception in case update failed.
- */
- public function update($runValidation = true, $attributes = null)
- {
- if ($runValidation && !$this->validate($attributes)) {
- return false;
- }
- $db = static::getDb();
- if ($this->isTransactional(self::OP_UPDATE)) {
- $transaction = $db->beginTransaction();
- try {
- $result = $this->updateInternal($attributes);
- if ($result === false) {
- $transaction->rollBack();
- } else {
- $transaction->commit();
- }
- } catch (\Exception $e) {
- $transaction->rollBack();
- throw $e;
- }
- } else {
- $result = $this->updateInternal($attributes);
- }
- return $result;
- }
-
- /**
- * Deletes the table row corresponding to this active record.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 2. delete the record from the database;
- * 3. call [[afterDelete()]].
- *
- * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
- * will be raised by the corresponding methods.
- *
- * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
- * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
- * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
- * being deleted is outdated.
- * @throws \Exception in case delete failed.
- */
- public function delete()
- {
- $db = static::getDb();
- if ($this->isTransactional(self::OP_DELETE)) {
- $transaction = $db->beginTransaction();
- try {
- $result = $this->deleteInternal();
- if ($result === false) {
- $transaction->rollBack();
- } else {
- $transaction->commit();
- }
- } catch (\Exception $e) {
- $transaction->rollBack();
- throw $e;
- }
- } else {
- $result = $this->deleteInternal();
- }
-
- return $result;
- }
-
- /**
- * Deletes an ActiveRecord without considering transaction.
- * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
- * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
- * @throws StaleObjectException
- */
- protected function deleteInternal()
- {
- $result = false;
- if ($this->beforeDelete()) {
- // we do not check the return value of deleteAll() because it's possible
- // the record is already deleted in the database and thus the method will return 0
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- $condition[$lock] = $this->$lock;
- }
- $result = $this->deleteAll($condition);
- if ($lock !== null && !$result) {
- throw new StaleObjectException('The object being deleted is outdated.');
- }
- $this->setOldAttributes(null);
- $this->afterDelete();
- }
- return $result;
- }
-
- /**
- * Returns a value indicating whether the given active record is the same as the current one.
- * The comparison is made by comparing the table names and the primary key values of the two active records.
- * If one of the records [[isNewRecord|is new]] they are also considered not equal.
- * @param ActiveRecord $record record to compare to
- * @return boolean whether the two active records refer to the same row in the same database table.
- */
- public function equals($record)
- {
- if ($this->isNewRecord || $record->isNewRecord) {
- return false;
- }
- return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey();
- }
-
- /**
- * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
- * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
- * @return boolean whether the specified operation is transactional in the current [[scenario]].
- */
- public function isTransactional($operation)
- {
- $scenario = $this->getScenario();
- $transactions = $this->transactions();
- return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
- }
+ /**
+ * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_INSERT = 0x01;
+ /**
+ * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_UPDATE = 0x02;
+ /**
+ * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_DELETE = 0x04;
+ /**
+ * All three operations: insert, update, delete.
+ * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
+ */
+ const OP_ALL = 0x07;
+
+ /**
+ * Returns the database connection used by this AR class.
+ * By default, the "db" application component is used as the database connection.
+ * You may override this method if you want to use a different database connection.
+ * @return Connection the database connection used by this AR class.
+ */
+ public static function getDb()
+ {
+ return \Yii::$app->getDb();
+ }
+
+ /**
+ * Creates an [[ActiveQuery]] instance with a given SQL statement.
+ *
+ * Note that because the SQL statement is already specified, calling additional
+ * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]]
+ * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is
+ * still fine.
+ *
+ * Below is an example:
+ *
+ * ~~~
+ * $customers = Customer::findBySql('SELECT * FROM tbl_customer')->all();
+ * ~~~
+ *
+ * @param string $sql the SQL statement to be executed
+ * @param array $params parameters to be bound to the SQL statement during execution.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance
+ */
+ public static function findBySql($sql, $params = [])
+ {
+ $query = static::createQuery();
+ $query->sql = $sql;
+
+ return $query->params($params);
+ }
+
+ /**
+ * Updates the whole table using the provided attribute values and conditions.
+ * For example, to change the status to be 1 for all customers whose status is 2:
+ *
+ * ~~~
+ * Customer::updateAll(['status' => 1], 'status = 2');
+ * ~~~
+ *
+ * @param array $attributes attribute values (name-value pairs) to be saved into the table
+ * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return integer the number of rows updated
+ */
+ public static function updateAll($attributes, $condition = '', $params = [])
+ {
+ $command = static::getDb()->createCommand();
+ $command->update(static::tableName(), $attributes, $condition, $params);
+
+ return $command->execute();
+ }
+
+ /**
+ * Updates the whole table using the provided counter changes and conditions.
+ * For example, to increment all customers' age by 1,
+ *
+ * ~~~
+ * Customer::updateAllCounters(['age' => 1]);
+ * ~~~
+ *
+ * @param array $counters the counters to be updated (attribute name => increment value).
+ * Use negative values if you want to decrement the counters.
+ * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method.
+ * @return integer the number of rows updated
+ */
+ public static function updateAllCounters($counters, $condition = '', $params = [])
+ {
+ $n = 0;
+ foreach ($counters as $name => $value) {
+ $counters[$name] = new Expression("[[$name]]+:bp{$n}", [":bp{$n}" => $value]);
+ $n++;
+ }
+ $command = static::getDb()->createCommand();
+ $command->update(static::tableName(), $counters, $condition, $params);
+
+ return $command->execute();
+ }
+
+ /**
+ * Deletes rows in the table using the provided conditions.
+ * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
+ *
+ * For example, to delete all customers whose status is 3:
+ *
+ * ~~~
+ * Customer::deleteAll('status = 3');
+ * ~~~
+ *
+ * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return integer the number of rows deleted
+ */
+ public static function deleteAll($condition = '', $params = [])
+ {
+ $command = static::getDb()->createCommand();
+ $command->delete(static::tableName(), $condition, $params);
+
+ return $command->execute();
+ }
+
+ /**
+ * Creates an [[ActiveQuery]] instance.
+ *
+ * This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
+ * by [[hasOne()]] and [[hasMany()]] to create a relational query.
+ * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+ * written for querying `Customer` purpose.)
+ *
+ * You may also define default conditions that should apply to all queries unless overridden:
+ *
+ * ```php
+ * public static function createQuery($config = [])
+ * {
+ * return parent::createQuery($config)->where(['deleted' => false]);
+ * }
+ * ```
+ *
+ * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
+ * default condition. Using [[Query::where()]] will override the default condition.
+ *
+ * @param array $config the configuration passed to the ActiveQuery class.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance.
+ */
+ public static function createQuery($config = [])
+ {
+ $config['modelClass'] = get_called_class();
+
+ return new ActiveQuery($config);
+ }
+
+ /**
+ * Declares the name of the database table associated with this AR class.
+ * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]
+ * with prefix [[Connection::tablePrefix]]. For example if [[Connection::tablePrefix]] is 'tbl_',
+ * 'Customer' becomes 'tbl_customer', and 'OrderItem' becomes 'tbl_order_item'. You may override this method
+ * if the table is not named after this convention.
+ * @return string the table name
+ */
+ public static function tableName()
+ {
+ return '{{%' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_') . '}}';
+ }
+
+ /**
+ * Returns the schema information of the DB table associated with this AR class.
+ * @return TableSchema the schema information of the DB table associated with this AR class.
+ * @throws InvalidConfigException if the table for the AR class does not exist.
+ */
+ public static function getTableSchema()
+ {
+ $schema = static::getDb()->getTableSchema(static::tableName());
+ if ($schema !== null) {
+ return $schema;
+ } else {
+ throw new InvalidConfigException("The table does not exist: " . static::tableName());
+ }
+ }
+
+ /**
+ * Returns the primary key name(s) for this AR class.
+ * The default implementation will return the primary key(s) as declared
+ * in the DB table that is associated with this AR class.
+ *
+ * If the DB table does not declare any primary key, you should override
+ * this method to return the attributes that you want to use as primary keys
+ * for this AR class.
+ *
+ * Note that an array should be returned even for a table with single primary key.
+ *
+ * @return string[] the primary keys of the associated database table.
+ */
+ public static function primaryKey()
+ {
+ return static::getTableSchema()->primaryKey;
+ }
+
+ /**
+ * Returns the list of all attribute names of the model.
+ * The default implementation will return all column names of the table associated with this AR class.
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ return array_keys(static::getTableSchema()->columns);
+ }
+
+ /**
+ * Declares which DB operations should be performed within a transaction in different scenarios.
+ * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
+ * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
+ * By default, these methods are NOT enclosed in a DB transaction.
+ *
+ * In some scenarios, to ensure data consistency, you may want to enclose some or all of them
+ * in transactions. You can do so by overriding this method and returning the operations
+ * that need to be transactional. For example,
+ *
+ * ~~~
+ * return [
+ * 'admin' => self::OP_INSERT,
+ * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
+ * // the above is equivalent to the following:
+ * // 'api' => self::OP_ALL,
+ *
+ * ];
+ * ~~~
+ *
+ * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
+ * should be done in a transaction; and in the "api" scenario, all the operations should be done
+ * in a transaction.
+ *
+ * @return array the declarations of transactional operations. The array keys are scenarios names,
+ * and the array values are the corresponding transaction operations.
+ */
+ public function transactions()
+ {
+ return [];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function populateRecord($record, $row)
+ {
+ $columns = static::getTableSchema()->columns;
+ foreach ($row as $name => $value) {
+ if (isset($columns[$name])) {
+ $row[$name] = $columns[$name]->typecast($value);
+ }
+ }
+ parent::populateRecord($record, $row);
+ }
+
+ /**
+ * Inserts a row into the associated database table using the attribute values of this record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. insert the record into database. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database.
+ *
+ * If the table's primary key is auto-incremental and is null during insertion,
+ * it will be populated with the actual value after insertion.
+ *
+ * For example, to insert a customer record:
+ *
+ * ~~~
+ * $customer = new Customer;
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->insert();
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return boolean whether the attributes are valid and the record is inserted successfully.
+ * @throws \Exception in case insert failed.
+ */
+ public function insert($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ $db = static::getDb();
+ if ($this->isTransactional(self::OP_INSERT)) {
+ $transaction = $db->beginTransaction();
+ try {
+ $result = $this->insertInternal($attributes);
+ if ($result === false) {
+ $transaction->rollBack();
+ } else {
+ $transaction->commit();
+ }
+ } catch (\Exception $e) {
+ $transaction->rollBack();
+ throw $e;
+ }
+ } else {
+ $result = $this->insertInternal($attributes);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Inserts an ActiveRecord into DB without considering transaction.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return boolean whether the record is inserted successfully.
+ */
+ protected function insertInternal($attributes = null)
+ {
+ if (!$this->beforeSave(true)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ foreach ($this->getPrimaryKey(true) as $key => $value) {
+ $values[$key] = $value;
+ }
+ }
+ $db = static::getDb();
+ $command = $db->createCommand()->insert($this->tableName(), $values);
+ if (!$command->execute()) {
+ return false;
+ }
+ $table = $this->getTableSchema();
+ if ($table->sequenceName !== null) {
+ foreach ($table->primaryKey as $name) {
+ if ($this->getAttribute($name) === null) {
+ $id = $db->getLastInsertID($table->sequenceName);
+ $this->setAttribute($name, $id);
+ $this->setOldAttribute($name, $id);
+ break;
+ }
+ }
+ }
+ foreach ($values as $name => $value) {
+ $this->setOldAttribute($name, $value);
+ }
+ $this->afterSave(true);
+
+ return true;
+ }
+
+ /**
+ * Saves the changes to this active record into the associated database table.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. save the record into database. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
+ *
+ * For example, to update a customer record:
+ *
+ * ~~~
+ * $customer = Customer::find($id);
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->update();
+ * ~~~
+ *
+ * Note that it is possible the update does not affect any row in the table.
+ * In this case, this method will return 0. For this reason, you should use the following
+ * code to check if update() is successful or not:
+ *
+ * ~~~
+ * if ($this->update() !== false) {
+ * // update successful
+ * } else {
+ * // update failed
+ * }
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return integer|boolean the number of rows affected, or false if validation fails
+ * or [[beforeSave()]] stops the updating process.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being updated is outdated.
+ * @throws \Exception in case update failed.
+ */
+ public function update($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ $db = static::getDb();
+ if ($this->isTransactional(self::OP_UPDATE)) {
+ $transaction = $db->beginTransaction();
+ try {
+ $result = $this->updateInternal($attributes);
+ if ($result === false) {
+ $transaction->rollBack();
+ } else {
+ $transaction->commit();
+ }
+ } catch (\Exception $e) {
+ $transaction->rollBack();
+ throw $e;
+ }
+ } else {
+ $result = $this->updateInternal($attributes);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Deletes the table row corresponding to this active record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 2. delete the record from the database;
+ * 3. call [[afterDelete()]].
+ *
+ * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
+ * will be raised by the corresponding methods.
+ *
+ * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+ * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being deleted is outdated.
+ * @throws \Exception in case delete failed.
+ */
+ public function delete()
+ {
+ $db = static::getDb();
+ if ($this->isTransactional(self::OP_DELETE)) {
+ $transaction = $db->beginTransaction();
+ try {
+ $result = $this->deleteInternal();
+ if ($result === false) {
+ $transaction->rollBack();
+ } else {
+ $transaction->commit();
+ }
+ } catch (\Exception $e) {
+ $transaction->rollBack();
+ throw $e;
+ }
+ } else {
+ $result = $this->deleteInternal();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Deletes an ActiveRecord without considering transaction.
+ * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+ * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
+ * @throws StaleObjectException
+ */
+ protected function deleteInternal()
+ {
+ $result = false;
+ if ($this->beforeDelete()) {
+ // we do not check the return value of deleteAll() because it's possible
+ // the record is already deleted in the database and thus the method will return 0
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ $condition[$lock] = $this->$lock;
+ }
+ $result = $this->deleteAll($condition);
+ if ($lock !== null && !$result) {
+ throw new StaleObjectException('The object being deleted is outdated.');
+ }
+ $this->setOldAttributes(null);
+ $this->afterDelete();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a value indicating whether the given active record is the same as the current one.
+ * The comparison is made by comparing the table names and the primary key values of the two active records.
+ * If one of the records [[isNewRecord|is new]] they are also considered not equal.
+ * @param ActiveRecord $record record to compare to
+ * @return boolean whether the two active records refer to the same row in the same database table.
+ */
+ public function equals($record)
+ {
+ if ($this->isNewRecord || $record->isNewRecord) {
+ return false;
+ }
+
+ return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey();
+ }
+
+ /**
+ * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
+ * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
+ * @return boolean whether the specified operation is transactional in the current [[scenario]].
+ */
+ public function isTransactional($operation)
+ {
+ $scenario = $this->getScenario();
+ $transactions = $this->transactions();
+
+ return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
+ }
}
diff --git a/framework/db/ActiveRecordInterface.php b/framework/db/ActiveRecordInterface.php
index 550ae25e752..31b0e097eee 100644
--- a/framework/db/ActiveRecordInterface.php
+++ b/framework/db/ActiveRecordInterface.php
@@ -1,7 +1,7 @@
*/
@@ -16,298 +16,298 @@
*/
interface ActiveRecordInterface
{
- /**
- * Returns the primary key **name(s)** for this AR class.
- *
- * Note that an array should be returned even when the record only has a single primary key.
- *
- * For the primary key **value** see [[getPrimaryKey()]] instead.
- *
- * @return string[] the primary key name(s) for this AR class.
- */
- public static function primaryKey();
+ /**
+ * Returns the primary key **name(s)** for this AR class.
+ *
+ * Note that an array should be returned even when the record only has a single primary key.
+ *
+ * For the primary key **value** see [[getPrimaryKey()]] instead.
+ *
+ * @return string[] the primary key name(s) for this AR class.
+ */
+ public static function primaryKey();
- /**
- * Returns the list of all attribute names of the record.
- * @return array list of attribute names.
- */
- public function attributes();
+ /**
+ * Returns the list of all attribute names of the record.
+ * @return array list of attribute names.
+ */
+ public function attributes();
- /**
- * Returns the named attribute value.
- * If this record is the result of a query and the attribute is not loaded,
- * null will be returned.
- * @param string $name the attribute name
- * @return mixed the attribute value. Null if the attribute is not set or does not exist.
- * @see hasAttribute()
- */
- public function getAttribute($name);
+ /**
+ * Returns the named attribute value.
+ * If this record is the result of a query and the attribute is not loaded,
+ * null will be returned.
+ * @param string $name the attribute name
+ * @return mixed the attribute value. Null if the attribute is not set or does not exist.
+ * @see hasAttribute()
+ */
+ public function getAttribute($name);
- /**
- * Sets the named attribute value.
- * @param string $name the attribute name.
- * @param mixed $value the attribute value.
- * @see hasAttribute()
- */
- public function setAttribute($name, $value);
+ /**
+ * Sets the named attribute value.
+ * @param string $name the attribute name.
+ * @param mixed $value the attribute value.
+ * @see hasAttribute()
+ */
+ public function setAttribute($name, $value);
- /**
- * Returns a value indicating whether the record has an attribute with the specified name.
- * @param string $name the name of the attribute
- * @return boolean whether the record has an attribute with the specified name.
- */
- public function hasAttribute($name);
+ /**
+ * Returns a value indicating whether the record has an attribute with the specified name.
+ * @param string $name the name of the attribute
+ * @return boolean whether the record has an attribute with the specified name.
+ */
+ public function hasAttribute($name);
- /**
- * Returns the primary key value(s).
- * @param boolean $asArray whether to return the primary key value as an array. If true,
- * the return value will be an array with attribute names as keys and attribute values as values.
- * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
- * @return mixed the primary key value. An array (attribute name => attribute value) is returned if the primary key
- * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
- * the key value is null).
- */
- public function getPrimaryKey($asArray = false);
+ /**
+ * Returns the primary key value(s).
+ * @param boolean $asArray whether to return the primary key value as an array. If true,
+ * the return value will be an array with attribute names as keys and attribute values as values.
+ * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
+ * @return mixed the primary key value. An array (attribute name => attribute value) is returned if the primary key
+ * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+ * the key value is null).
+ */
+ public function getPrimaryKey($asArray = false);
- /**
- * Returns the old primary key value(s).
- * This refers to the primary key value that is populated into the record
- * after executing a find method (e.g. find(), findAll()).
- * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
- * @param boolean $asArray whether to return the primary key value as an array. If true,
- * the return value will be an array with column name as key and column value as value.
- * If this is false (default), a scalar value will be returned for non-composite primary key.
- * @property mixed The old primary key value. An array (column name => column value) is
- * returned if the primary key is composite. A string is returned otherwise (null will be
- * returned if the key value is null).
- * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
- * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
- * the key value is null).
- */
- public function getOldPrimaryKey($asArray = false);
+ /**
+ * Returns the old primary key value(s).
+ * This refers to the primary key value that is populated into the record
+ * after executing a find method (e.g. find(), findAll()).
+ * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
+ * @param boolean $asArray whether to return the primary key value as an array. If true,
+ * the return value will be an array with column name as key and column value as value.
+ * If this is false (default), a scalar value will be returned for non-composite primary key.
+ * @property mixed The old primary key value. An array (column name => column value) is
+ * returned if the primary key is composite. A string is returned otherwise (null will be
+ * returned if the key value is null).
+ * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
+ * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+ * the key value is null).
+ */
+ public function getOldPrimaryKey($asArray = false);
- /**
- * Returns a value indicating whether the given set of attributes represents the primary key for this model
- * @param array $keys the set of attributes to check
- * @return boolean whether the given set of attributes represents the primary key for this model
- */
- public static function isPrimaryKey($keys);
+ /**
+ * Returns a value indicating whether the given set of attributes represents the primary key for this model
+ * @param array $keys the set of attributes to check
+ * @return boolean whether the given set of attributes represents the primary key for this model
+ */
+ public static function isPrimaryKey($keys);
- /**
- * Creates an [[ActiveQueryInterface|ActiveQuery]] instance for query purpose.
- *
- * This method is usually ment to be used like this:
- *
- * ```php
- * Customer::find(1); // find one customer by primary key
- * Customer::find()->all(); // find all customers
- * ```
- *
- * @param mixed $q the query parameter. This can be one of the followings:
- *
- * - a scalar value (integer or string): query by a single primary key value and return the
- * corresponding record.
- * - an array of name-value pairs: query by a set of attribute values and return a single record matching all of them.
- * - null (not specified): return a new [[ActiveQuery]] object for further query purpose.
- *
- * @return ActiveQueryInterface|static|null When `$q` is null, a new [[ActiveQuery]] instance
- * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
- * returned (null will be returned if there is no matching).
- */
- public static function find($q = null);
+ /**
+ * Creates an [[ActiveQueryInterface|ActiveQuery]] instance for query purpose.
+ *
+ * This method is usually ment to be used like this:
+ *
+ * ```php
+ * Customer::find(1); // find one customer by primary key
+ * Customer::find()->all(); // find all customers
+ * ```
+ *
+ * @param mixed $q the query parameter. This can be one of the followings:
+ *
+ * - a scalar value (integer or string): query by a single primary key value and return the
+ * corresponding record.
+ * - an array of name-value pairs: query by a set of attribute values and return a single record matching all of them.
+ * - null (not specified): return a new [[ActiveQuery]] object for further query purpose.
+ *
+ * @return ActiveQueryInterface|static|null When `$q` is null, a new [[ActiveQuery]] instance
+ * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
+ * returned (null will be returned if there is no matching).
+ */
+ public static function find($q = null);
- /**
- * Creates an [[ActiveQueryInterface|ActiveQuery]] instance.
- *
- * This method is called by [[find()]] to start a SELECT query but also
- * by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to
- * create a relational query.
- *
- * You may override this method to return a customized query (e.g. `CustomerQuery` specified
- * written for querying `Customer` purpose.)
- *
- * You may also define default conditions that should apply to all queries unless overridden:
- *
- * ```php
- * public static function createQuery($config = [])
- * {
- * return parent::createQuery($config)->where(['deleted' => false]);
- * }
- * ```
- *
- * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
- * default condition. Using [[Query::where()]] will override the default condition.
- *
- * @param array $config the configuration passed to the ActiveQuery class.
- * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
- */
- public static function createQuery($config = []);
+ /**
+ * Creates an [[ActiveQueryInterface|ActiveQuery]] instance.
+ *
+ * This method is called by [[find()]] to start a SELECT query but also
+ * by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to
+ * create a relational query.
+ *
+ * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+ * written for querying `Customer` purpose.)
+ *
+ * You may also define default conditions that should apply to all queries unless overridden:
+ *
+ * ```php
+ * public static function createQuery($config = [])
+ * {
+ * return parent::createQuery($config)->where(['deleted' => false]);
+ * }
+ * ```
+ *
+ * Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
+ * default condition. Using [[Query::where()]] will override the default condition.
+ *
+ * @param array $config the configuration passed to the ActiveQuery class.
+ * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
+ */
+ public static function createQuery($config = []);
- /**
- * Updates records using the provided attribute values and conditions.
- * For example, to change the status to be 1 for all customers whose status is 2:
- *
- * ~~~
- * Customer::updateAll(['status' => 1], ['status' => '2']);
- * ~~~
- *
- * @param array $attributes attribute values (name-value pairs) to be saved for the record.
- * Unlike [[update()]] these are not going to be validated.
- * @param array $condition the condition that matches the records that should get updated.
- * Please refer to [[QueryInterface::where()]] on how to specify this parameter.
- * An empty condition will match all records.
- * @return integer the number of rows updated
- */
- public static function updateAll($attributes, $condition = null);
+ /**
+ * Updates records using the provided attribute values and conditions.
+ * For example, to change the status to be 1 for all customers whose status is 2:
+ *
+ * ~~~
+ * Customer::updateAll(['status' => 1], ['status' => '2']);
+ * ~~~
+ *
+ * @param array $attributes attribute values (name-value pairs) to be saved for the record.
+ * Unlike [[update()]] these are not going to be validated.
+ * @param array $condition the condition that matches the records that should get updated.
+ * Please refer to [[QueryInterface::where()]] on how to specify this parameter.
+ * An empty condition will match all records.
+ * @return integer the number of rows updated
+ */
+ public static function updateAll($attributes, $condition = null);
- /**
- * Deletes records using the provided conditions.
- * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
- *
- * For example, to delete all customers whose status is 3:
- *
- * ~~~
- * Customer::deleteAll([status = 3]);
- * ~~~
- *
- * @param array $condition the condition that matches the records that should get deleted.
- * Please refer to [[QueryInterface::where()]] on how to specify this parameter.
- * An empty condition will match all records.
- * @return integer the number of rows deleted
- */
- public static function deleteAll($condition = null);
+ /**
+ * Deletes records using the provided conditions.
+ * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
+ *
+ * For example, to delete all customers whose status is 3:
+ *
+ * ~~~
+ * Customer::deleteAll([status = 3]);
+ * ~~~
+ *
+ * @param array $condition the condition that matches the records that should get deleted.
+ * Please refer to [[QueryInterface::where()]] on how to specify this parameter.
+ * An empty condition will match all records.
+ * @return integer the number of rows deleted
+ */
+ public static function deleteAll($condition = null);
- /**
- * Saves the current record.
- *
- * This method will call [[insert()]] when [[getIsNewRecord|isNewRecord]] is true, or [[update()]]
- * when [[getIsNewRecord|isNewRecord]] is false.
- *
- * For example, to save a customer record:
- *
- * ~~~
- * $customer = new Customer; // or $customer = Customer::find($id);
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->save();
- * ~~~
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be saved to database. `false` will be returned
- * in this case.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return boolean whether the saving succeeds
- */
- public function save($runValidation = true, $attributes = null);
+ /**
+ * Saves the current record.
+ *
+ * This method will call [[insert()]] when [[getIsNewRecord|isNewRecord]] is true, or [[update()]]
+ * when [[getIsNewRecord|isNewRecord]] is false.
+ *
+ * For example, to save a customer record:
+ *
+ * ~~~
+ * $customer = new Customer; // or $customer = Customer::find($id);
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->save();
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be saved to database. `false` will be returned
+ * in this case.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return boolean whether the saving succeeds
+ */
+ public function save($runValidation = true, $attributes = null);
- /**
- * Inserts the record into the database using the attribute values of this record.
- *
- * Usage example:
- *
- * ```php
- * $customer = new Customer;
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->insert();
- * ```
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted into the database.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return boolean whether the attributes are valid and the record is inserted successfully.
- */
- public function insert($runValidation = true, $attributes = null);
+ /**
+ * Inserts the record into the database using the attribute values of this record.
+ *
+ * Usage example:
+ *
+ * ```php
+ * $customer = new Customer;
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->insert();
+ * ```
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return boolean whether the attributes are valid and the record is inserted successfully.
+ */
+ public function insert($runValidation = true, $attributes = null);
- /**
- * Saves the changes to this active record into the database.
- *
- * Usage example:
- *
- * ```php
- * $customer = Customer::find($id);
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->update();
- * ```
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted into the database.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return integer|boolean the number of rows affected, or false if validation fails
- * or updating process is stopped for other reasons.
- * Note that it is possible that the number of rows affected is 0, even though the
- * update execution is successful.
- */
- public function update($runValidation = true, $attributes = null);
+ /**
+ * Saves the changes to this active record into the database.
+ *
+ * Usage example:
+ *
+ * ```php
+ * $customer = Customer::find($id);
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->update();
+ * ```
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return integer|boolean the number of rows affected, or false if validation fails
+ * or updating process is stopped for other reasons.
+ * Note that it is possible that the number of rows affected is 0, even though the
+ * update execution is successful.
+ */
+ public function update($runValidation = true, $attributes = null);
- /**
- * Deletes the record from the database.
- *
- * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
- * Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful.
- */
- public function delete();
+ /**
+ * Deletes the record from the database.
+ *
+ * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+ * Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful.
+ */
+ public function delete();
- /**
- * Returns a value indicating whether the current record is new (not saved in the database).
- * @return boolean whether the record is new and should be inserted when calling [[save()]].
- */
- public function getIsNewRecord();
+ /**
+ * Returns a value indicating whether the current record is new (not saved in the database).
+ * @return boolean whether the record is new and should be inserted when calling [[save()]].
+ */
+ public function getIsNewRecord();
- /**
- * Returns a value indicating whether the given active record is the same as the current one.
- * Two [[isNewRecord|new]] records are considered to be not equal.
- * @param static $record record to compare to
- * @return boolean whether the two active records refer to the same row in the same database table.
- */
- public function equals($record);
+ /**
+ * Returns a value indicating whether the given active record is the same as the current one.
+ * Two [[isNewRecord|new]] records are considered to be not equal.
+ * @param static $record record to compare to
+ * @return boolean whether the two active records refer to the same row in the same database table.
+ */
+ public function equals($record);
- /**
- * Returns the relation object with the specified name.
- * A relation is defined by a getter method which returns an object implementing the [[ActiveQueryInterface]]
- * (normally this would be a relational [[ActiveQuery]] object).
- * It can be declared in either the ActiveRecord class itself or one of its behaviors.
- * @param string $name the relation name
- * @param boolean $throwException whether to throw exception if the relation does not exist.
- * @return ActiveQueryInterface the relational query object
- */
- public function getRelation($name, $throwException = true);
+ /**
+ * Returns the relation object with the specified name.
+ * A relation is defined by a getter method which returns an object implementing the [[ActiveQueryInterface]]
+ * (normally this would be a relational [[ActiveQuery]] object).
+ * It can be declared in either the ActiveRecord class itself or one of its behaviors.
+ * @param string $name the relation name
+ * @param boolean $throwException whether to throw exception if the relation does not exist.
+ * @return ActiveQueryInterface the relational query object
+ */
+ public function getRelation($name, $throwException = true);
- /**
- * Establishes the relationship between two records.
- *
- * The relationship is established by setting the foreign key value(s) in one record
- * to be the corresponding primary key value(s) in the other record.
- * The record with the foreign key will be saved into database without performing validation.
- *
- * If the relationship involves a pivot table, a new row will be inserted into the
- * pivot table which contains the primary key values from both records.
- *
- * This method requires that the primary key value is not null.
- *
- * @param string $name the case sensitive name of the relationship.
- * @param static $model the record to be linked with the current one.
- * @param array $extraColumns additional column values to be saved into the pivot table.
- * This parameter is only meaningful for a relationship involving a pivot table
- * (i.e., a relation set with `[[ActiveQueryInterface::via()]]`.)
- */
- public function link($name, $model, $extraColumns = []);
+ /**
+ * Establishes the relationship between two records.
+ *
+ * The relationship is established by setting the foreign key value(s) in one record
+ * to be the corresponding primary key value(s) in the other record.
+ * The record with the foreign key will be saved into database without performing validation.
+ *
+ * If the relationship involves a pivot table, a new row will be inserted into the
+ * pivot table which contains the primary key values from both records.
+ *
+ * This method requires that the primary key value is not null.
+ *
+ * @param string $name the case sensitive name of the relationship.
+ * @param static $model the record to be linked with the current one.
+ * @param array $extraColumns additional column values to be saved into the pivot table.
+ * This parameter is only meaningful for a relationship involving a pivot table
+ * (i.e., a relation set with `[[ActiveQueryInterface::via()]]`.)
+ */
+ public function link($name, $model, $extraColumns = []);
- /**
- * Destroys the relationship between two records.
- *
- * The record with the foreign key of the relationship will be deleted if `$delete` is true.
- * Otherwise, the foreign key will be set null and the record will be saved without validation.
- *
- * @param string $name the case sensitive name of the relationship.
- * @param static $model the model to be unlinked from the current one.
- * @param boolean $delete whether to delete the model that contains the foreign key.
- * If false, the model's foreign key will be set null and saved.
- * If true, the model containing the foreign key will be deleted.
- */
- public function unlink($name, $model, $delete = false);
+ /**
+ * Destroys the relationship between two records.
+ *
+ * The record with the foreign key of the relationship will be deleted if `$delete` is true.
+ * Otherwise, the foreign key will be set null and the record will be saved without validation.
+ *
+ * @param string $name the case sensitive name of the relationship.
+ * @param static $model the model to be unlinked from the current one.
+ * @param boolean $delete whether to delete the model that contains the foreign key.
+ * If false, the model's foreign key will be set null and saved.
+ * If true, the model containing the foreign key will be deleted.
+ */
+ public function unlink($name, $model, $delete = false);
}
diff --git a/framework/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php
index 860be0597cb..6676165e234 100644
--- a/framework/db/ActiveRelationTrait.php
+++ b/framework/db/ActiveRelationTrait.php
@@ -19,403 +19,410 @@
*/
trait ActiveRelationTrait
{
- /**
- * @var boolean whether this query represents a relation to more than one record.
- * This property is only used in relational context. If true, this relation will
- * populate all query results into AR instances using [[all()]].
- * If false, only the first row of the results will be retrieved using [[one()]].
- */
- public $multiple;
- /**
- * @var ActiveRecord the primary model of a relational query.
- * This is used only in lazy loading with dynamic query options.
- */
- public $primaryModel;
- /**
- * @var array the columns of the primary and foreign tables that establish a relation.
- * The array keys must be columns of the table for this relation, and the array values
- * must be the corresponding columns from the primary table.
- * Do not prefix or quote the column names as this will be done automatically by Yii.
- * This property is only used in relational context.
- */
- public $link;
- /**
- * @var array the query associated with the pivot table. Please call [[via()]]
- * to set this property instead of directly setting it.
- * This property is only used in relational context.
- * @see via()
- */
- public $via;
- /**
- * @var string the name of the relation that is the inverse of this relation.
- * For example, an order has a customer, which means the inverse of the "customer" relation
- * is the "orders", and the inverse of the "orders" relation is the "customer".
- * If this property is set, the primary record(s) will be referenced through the specified relation.
- * For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
- * and accessing the customer of an order will not trigger new DB query.
- * This property is only used in relational context.
- * @see inverseOf()
- */
- public $inverseOf;
-
-
- /**
- * Clones internal objects.
- */
- public function __clone()
- {
- parent::__clone();
- // make a clone of "via" object so that the same query object can be reused multiple times
- if (is_object($this->via)) {
- $this->via = clone $this->via;
- } elseif (is_array($this->via)) {
- $this->via = [$this->via[0], clone $this->via[1]];
- }
- }
-
- /**
- * Specifies the relation associated with the pivot table.
- *
- * Use this method to specify a pivot record/table when declaring a relation in the [[ActiveRecord]] class:
- *
- * ```php
- * public function getOrders()
- * {
- * return $this->hasOne(Order::className(), ['id' => 'order_id']);
- * }
- *
- * public function getOrderItems()
- * {
- * return $this->hasMany(Item::className(), ['id' => 'item_id'])
- * ->via('orders', ['order_id' => 'id']);
- * }
- * ```
- *
- * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
- * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
- * Its signature should be `function($query)`, where `$query` is the query to be customized.
- * @return static the relation object itself.
- */
- public function via($relationName, $callable = null)
- {
- $relation = $this->primaryModel->getRelation($relationName);
- $this->via = [$relationName, $relation];
- if ($callable !== null) {
- call_user_func($callable, $relation);
- }
- return $this;
- }
-
- /**
- * Sets the name of the relation that is the inverse of this relation.
- * For example, an order has a customer, which means the inverse of the "customer" relation
- * is the "orders", and the inverse of the "orders" relation is the "customer".
- * If this property is set, the primary record(s) will be referenced through the specified relation.
- * For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
- * and accessing the customer of an order will not trigger a new DB query.
- *
- * Use this method when declaring a relation in the [[ActiveRecord]] class:
- *
- * ```php
- * public function getOrders()
- * {
- * return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
- * }
- * ```
- *
- * @param string $relationName the name of the relation that is the inverse of this relation.
- * @return static the relation object itself.
- */
- public function inverseOf($relationName)
- {
- $this->inverseOf = $relationName;
- return $this;
- }
-
- /**
- * Finds the related records for the specified primary record.
- * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
- * @param string $name the relation name
- * @param ActiveRecordInterface|BaseActiveRecord $model the primary model
- * @return mixed the related record(s)
- * @throws InvalidParamException if the relation is invalid
- */
- public function findFor($name, $model)
- {
- if (method_exists($model, 'get' . $name)) {
- $method = new \ReflectionMethod($model, 'get' . $name);
- $realName = lcfirst(substr($method->getName(), 3));
- if ($realName !== $name) {
- throw new InvalidParamException('Relation names are case sensitive. ' . get_class($model) . " has a relation named \"$realName\" instead of \"$name\".");
- }
- }
-
- $related = $this->multiple ? $this->all() : $this->one();
-
- if ($this->inverseOf === null || empty($related)) {
- return $related;
- }
-
- $inverseRelation = (new $this->modelClass)->getRelation($this->inverseOf);
-
- if ($this->multiple) {
- foreach ($related as $i => $relatedModel) {
- if ($relatedModel instanceof ActiveRecordInterface) {
- $relatedModel->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model);
- } else {
- $related[$i][$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model;
- }
- }
- } else {
- if ($related instanceof ActiveRecordInterface) {
- $related->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model);
- } else {
- $related[$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model;
- }
- }
-
- return $related;
- }
-
- /**
- * Finds the related records and populates them into the primary models.
- * @param string $name the relation name
- * @param array $primaryModels primary models
- * @return array the related models
- * @throws InvalidConfigException if [[link]] is invalid
- */
- public function populateRelation($name, &$primaryModels)
- {
- if (!is_array($this->link)) {
- throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
- }
-
- if ($this->via instanceof self) {
- // via pivot table
- /** @var ActiveRelationTrait $viaQuery */
- $viaQuery = $this->via;
- $viaModels = $viaQuery->findPivotRows($primaryModels);
- $this->filterByModels($viaModels);
- } elseif (is_array($this->via)) {
- // via relation
- /** @var ActiveRelationTrait $viaQuery */
- list($viaName, $viaQuery) = $this->via;
- $viaQuery->primaryModel = null;
- $viaModels = $viaQuery->populateRelation($viaName, $primaryModels);
- $this->filterByModels($viaModels);
- } else {
- $this->filterByModels($primaryModels);
- }
-
- if (count($primaryModels) === 1 && !$this->multiple) {
- $model = $this->one();
- foreach ($primaryModels as $i => $primaryModel) {
- if ($primaryModel instanceof ActiveRecordInterface) {
- $primaryModel->populateRelation($name, $model);
- } else {
- $primaryModels[$i][$name] = $model;
- }
- if ($this->inverseOf !== null) {
- $this->populateInverseRelation($primaryModels, [$model], $name, $this->inverseOf);
- }
- }
- return [$model];
- } else {
- $models = $this->all();
- if (isset($viaModels, $viaQuery)) {
- $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
- } else {
- $buckets = $this->buildBuckets($models, $this->link);
- }
-
- $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
- foreach ($primaryModels as $i => $primaryModel) {
- $key = $this->getModelKey($primaryModel, $link);
- $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null);
- if ($primaryModel instanceof ActiveRecordInterface) {
- $primaryModel->populateRelation($name, $value);
- } else {
- $primaryModels[$i][$name] = $value;
- }
- }
- if ($this->inverseOf !== null) {
- $this->populateInverseRelation($primaryModels, $models, $name, $this->inverseOf);
- }
- return $models;
- }
- }
-
- private function populateInverseRelation(&$primaryModels, $models, $primaryName, $name)
- {
- if (empty($models) || empty($primaryModels)) {
- return;
- }
- $model = reset($models);
- $relation = $model instanceof ActiveRecordInterface ? $model->getRelation($name) : (new $this->modelClass)->getRelation($name);
-
- if ($relation->multiple) {
- $buckets = $this->buildBuckets($primaryModels, $relation->link, null, null, false);
- if ($model instanceof ActiveRecordInterface) {
- foreach ($models as $model) {
- $key = $this->getModelKey($model, $relation->link);
- $model->populateRelation($name, isset($buckets[$key]) ? $buckets[$key] : []);
- }
- } else {
- foreach ($primaryModels as $i => $primaryModel) {
- if ($this->multiple) {
- foreach ($primaryModel as $j => $m) {
- $key = $this->getModelKey($m, $relation->link);
- $primaryModels[$i][$j][$name] = isset($buckets[$key]) ? $buckets[$key] : [];
- }
- } elseif (!empty($primaryModel[$primaryName])) {
- $key = $this->getModelKey($primaryModel[$primaryName], $relation->link);
- $primaryModels[$i][$primaryName][$name] = isset($buckets[$key]) ? $buckets[$key] : [];
- }
- }
- }
- } else {
- if ($this->multiple) {
- foreach ($primaryModels as $i => $primaryModel) {
- foreach ($primaryModel[$primaryName] as $j => $m) {
- if ($m instanceof ActiveRecordInterface) {
- $m->populateRelation($name, $primaryModel);
- } else {
- $primaryModels[$i][$primaryName][$j][$name] = $primaryModel;
- }
- }
- }
- } else {
- foreach ($primaryModels as $i => $primaryModel) {
- if ($primaryModels[$i][$primaryName] instanceof ActiveRecordInterface) {
- $primaryModels[$i][$primaryName]->populateRelation($name, $primaryModel);
- } elseif (!empty($primaryModels[$i][$primaryName])) {
- $primaryModels[$i][$primaryName][$name] = $primaryModel;
- }
- }
- }
- }
- }
-
- /**
- * @param array $models
- * @param array $link
- * @param array $viaModels
- * @param array $viaLink
- * @param boolean $checkMultiple
- * @return array
- */
- private function buildBuckets($models, $link, $viaModels = null, $viaLink = null, $checkMultiple = true)
- {
- if ($viaModels !== null) {
- $map = [];
- $viaLinkKeys = array_keys($viaLink);
- $linkValues = array_values($link);
- foreach ($viaModels as $viaModel) {
- $key1 = $this->getModelKey($viaModel, $viaLinkKeys);
- $key2 = $this->getModelKey($viaModel, $linkValues);
- $map[$key2][$key1] = true;
- }
- }
-
- $buckets = [];
- $linkKeys = array_keys($link);
-
- if (isset($map)) {
- foreach ($models as $i => $model) {
- $key = $this->getModelKey($model, $linkKeys);
- if (isset($map[$key])) {
- foreach (array_keys($map[$key]) as $key2) {
- if ($this->indexBy !== null) {
- $buckets[$key2][$i] = $model;
- } else {
- $buckets[$key2][] = $model;
- }
- }
- }
- }
- } else {
- foreach ($models as $i => $model) {
- $key = $this->getModelKey($model, $linkKeys);
- if ($this->indexBy !== null) {
- $buckets[$key][$i] = $model;
- } else {
- $buckets[$key][] = $model;
- }
- }
- }
-
- if ($checkMultiple && !$this->multiple) {
- foreach ($buckets as $i => $bucket) {
- $buckets[$i] = reset($bucket);
- }
- }
- return $buckets;
- }
-
- /**
- * @param array $models
- */
- private function filterByModels($models)
- {
- $attributes = array_keys($this->link);
- $values = [];
- if (count($attributes) === 1) {
- // single key
- $attribute = reset($this->link);
- foreach ($models as $model) {
- if (($value = $model[$attribute]) !== null) {
- $values[] = $value;
- }
- }
- } else {
- // composite keys
- foreach ($models as $model) {
- $v = [];
- foreach ($this->link as $attribute => $link) {
- $v[$attribute] = $model[$link];
- }
- $values[] = $v;
- }
- }
- $this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);
- }
-
- /**
- * @param ActiveRecord|array $model
- * @param array $attributes
- * @return string
- */
- private function getModelKey($model, $attributes)
- {
- if (count($attributes) > 1) {
- $key = [];
- foreach ($attributes as $attribute) {
- $key[] = $model[$attribute];
- }
- return serialize($key);
- } else {
- $attribute = reset($attributes);
- $key = $model[$attribute];
- return is_scalar($key) ? $key : serialize($key);
- }
- }
-
- /**
- * @param array $primaryModels either array of AR instances or arrays
- * @return array
- */
- private function findPivotRows($primaryModels)
- {
- if (empty($primaryModels)) {
- return [];
- }
- $this->filterByModels($primaryModels);
- /** @var ActiveRecord $primaryModel */
- $primaryModel = reset($primaryModels);
- if (!$primaryModel instanceof ActiveRecordInterface) {
- // when primaryModels are array of arrays (asArray case)
- $primaryModel = new $this->modelClass;
- }
- return $this->asArray()->all($primaryModel->getDb());
- }
+ /**
+ * @var boolean whether this query represents a relation to more than one record.
+ * This property is only used in relational context. If true, this relation will
+ * populate all query results into AR instances using [[all()]].
+ * If false, only the first row of the results will be retrieved using [[one()]].
+ */
+ public $multiple;
+ /**
+ * @var ActiveRecord the primary model of a relational query.
+ * This is used only in lazy loading with dynamic query options.
+ */
+ public $primaryModel;
+ /**
+ * @var array the columns of the primary and foreign tables that establish a relation.
+ * The array keys must be columns of the table for this relation, and the array values
+ * must be the corresponding columns from the primary table.
+ * Do not prefix or quote the column names as this will be done automatically by Yii.
+ * This property is only used in relational context.
+ */
+ public $link;
+ /**
+ * @var array the query associated with the pivot table. Please call [[via()]]
+ * to set this property instead of directly setting it.
+ * This property is only used in relational context.
+ * @see via()
+ */
+ public $via;
+ /**
+ * @var string the name of the relation that is the inverse of this relation.
+ * For example, an order has a customer, which means the inverse of the "customer" relation
+ * is the "orders", and the inverse of the "orders" relation is the "customer".
+ * If this property is set, the primary record(s) will be referenced through the specified relation.
+ * For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
+ * and accessing the customer of an order will not trigger new DB query.
+ * This property is only used in relational context.
+ * @see inverseOf()
+ */
+ public $inverseOf;
+
+ /**
+ * Clones internal objects.
+ */
+ public function __clone()
+ {
+ parent::__clone();
+ // make a clone of "via" object so that the same query object can be reused multiple times
+ if (is_object($this->via)) {
+ $this->via = clone $this->via;
+ } elseif (is_array($this->via)) {
+ $this->via = [$this->via[0], clone $this->via[1]];
+ }
+ }
+
+ /**
+ * Specifies the relation associated with the pivot table.
+ *
+ * Use this method to specify a pivot record/table when declaring a relation in the [[ActiveRecord]] class:
+ *
+ * ```php
+ * public function getOrders()
+ * {
+ * return $this->hasOne(Order::className(), ['id' => 'order_id']);
+ * }
+ *
+ * public function getOrderItems()
+ * {
+ * return $this->hasMany(Item::className(), ['id' => 'item_id'])
+ * ->via('orders', ['order_id' => 'id']);
+ * }
+ * ```
+ *
+ * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
+ * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
+ * Its signature should be `function($query)`, where `$query` is the query to be customized.
+ * @return static the relation object itself.
+ */
+ public function via($relationName, $callable = null)
+ {
+ $relation = $this->primaryModel->getRelation($relationName);
+ $this->via = [$relationName, $relation];
+ if ($callable !== null) {
+ call_user_func($callable, $relation);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the name of the relation that is the inverse of this relation.
+ * For example, an order has a customer, which means the inverse of the "customer" relation
+ * is the "orders", and the inverse of the "orders" relation is the "customer".
+ * If this property is set, the primary record(s) will be referenced through the specified relation.
+ * For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
+ * and accessing the customer of an order will not trigger a new DB query.
+ *
+ * Use this method when declaring a relation in the [[ActiveRecord]] class:
+ *
+ * ```php
+ * public function getOrders()
+ * {
+ * return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
+ * }
+ * ```
+ *
+ * @param string $relationName the name of the relation that is the inverse of this relation.
+ * @return static the relation object itself.
+ */
+ public function inverseOf($relationName)
+ {
+ $this->inverseOf = $relationName;
+
+ return $this;
+ }
+
+ /**
+ * Finds the related records for the specified primary record.
+ * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
+ * @param string $name the relation name
+ * @param ActiveRecordInterface|BaseActiveRecord $model the primary model
+ * @return mixed the related record(s)
+ * @throws InvalidParamException if the relation is invalid
+ */
+ public function findFor($name, $model)
+ {
+ if (method_exists($model, 'get' . $name)) {
+ $method = new \ReflectionMethod($model, 'get' . $name);
+ $realName = lcfirst(substr($method->getName(), 3));
+ if ($realName !== $name) {
+ throw new InvalidParamException('Relation names are case sensitive. ' . get_class($model) . " has a relation named \"$realName\" instead of \"$name\".");
+ }
+ }
+
+ $related = $this->multiple ? $this->all() : $this->one();
+
+ if ($this->inverseOf === null || empty($related)) {
+ return $related;
+ }
+
+ $inverseRelation = (new $this->modelClass)->getRelation($this->inverseOf);
+
+ if ($this->multiple) {
+ foreach ($related as $i => $relatedModel) {
+ if ($relatedModel instanceof ActiveRecordInterface) {
+ $relatedModel->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model);
+ } else {
+ $related[$i][$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model;
+ }
+ }
+ } else {
+ if ($related instanceof ActiveRecordInterface) {
+ $related->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model);
+ } else {
+ $related[$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model;
+ }
+ }
+
+ return $related;
+ }
+
+ /**
+ * Finds the related records and populates them into the primary models.
+ * @param string $name the relation name
+ * @param array $primaryModels primary models
+ * @return array the related models
+ * @throws InvalidConfigException if [[link]] is invalid
+ */
+ public function populateRelation($name, &$primaryModels)
+ {
+ if (!is_array($this->link)) {
+ throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
+ }
+
+ if ($this->via instanceof self) {
+ // via pivot table
+ /** @var ActiveRelationTrait $viaQuery */
+ $viaQuery = $this->via;
+ $viaModels = $viaQuery->findPivotRows($primaryModels);
+ $this->filterByModels($viaModels);
+ } elseif (is_array($this->via)) {
+ // via relation
+ /** @var ActiveRelationTrait $viaQuery */
+ list($viaName, $viaQuery) = $this->via;
+ $viaQuery->primaryModel = null;
+ $viaModels = $viaQuery->populateRelation($viaName, $primaryModels);
+ $this->filterByModels($viaModels);
+ } else {
+ $this->filterByModels($primaryModels);
+ }
+
+ if (count($primaryModels) === 1 && !$this->multiple) {
+ $model = $this->one();
+ foreach ($primaryModels as $i => $primaryModel) {
+ if ($primaryModel instanceof ActiveRecordInterface) {
+ $primaryModel->populateRelation($name, $model);
+ } else {
+ $primaryModels[$i][$name] = $model;
+ }
+ if ($this->inverseOf !== null) {
+ $this->populateInverseRelation($primaryModels, [$model], $name, $this->inverseOf);
+ }
+ }
+
+ return [$model];
+ } else {
+ $models = $this->all();
+ if (isset($viaModels, $viaQuery)) {
+ $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
+ } else {
+ $buckets = $this->buildBuckets($models, $this->link);
+ }
+
+ $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
+ foreach ($primaryModels as $i => $primaryModel) {
+ $key = $this->getModelKey($primaryModel, $link);
+ $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null);
+ if ($primaryModel instanceof ActiveRecordInterface) {
+ $primaryModel->populateRelation($name, $value);
+ } else {
+ $primaryModels[$i][$name] = $value;
+ }
+ }
+ if ($this->inverseOf !== null) {
+ $this->populateInverseRelation($primaryModels, $models, $name, $this->inverseOf);
+ }
+
+ return $models;
+ }
+ }
+
+ private function populateInverseRelation(&$primaryModels, $models, $primaryName, $name)
+ {
+ if (empty($models) || empty($primaryModels)) {
+ return;
+ }
+ $model = reset($models);
+ $relation = $model instanceof ActiveRecordInterface ? $model->getRelation($name) : (new $this->modelClass)->getRelation($name);
+
+ if ($relation->multiple) {
+ $buckets = $this->buildBuckets($primaryModels, $relation->link, null, null, false);
+ if ($model instanceof ActiveRecordInterface) {
+ foreach ($models as $model) {
+ $key = $this->getModelKey($model, $relation->link);
+ $model->populateRelation($name, isset($buckets[$key]) ? $buckets[$key] : []);
+ }
+ } else {
+ foreach ($primaryModels as $i => $primaryModel) {
+ if ($this->multiple) {
+ foreach ($primaryModel as $j => $m) {
+ $key = $this->getModelKey($m, $relation->link);
+ $primaryModels[$i][$j][$name] = isset($buckets[$key]) ? $buckets[$key] : [];
+ }
+ } elseif (!empty($primaryModel[$primaryName])) {
+ $key = $this->getModelKey($primaryModel[$primaryName], $relation->link);
+ $primaryModels[$i][$primaryName][$name] = isset($buckets[$key]) ? $buckets[$key] : [];
+ }
+ }
+ }
+ } else {
+ if ($this->multiple) {
+ foreach ($primaryModels as $i => $primaryModel) {
+ foreach ($primaryModel[$primaryName] as $j => $m) {
+ if ($m instanceof ActiveRecordInterface) {
+ $m->populateRelation($name, $primaryModel);
+ } else {
+ $primaryModels[$i][$primaryName][$j][$name] = $primaryModel;
+ }
+ }
+ }
+ } else {
+ foreach ($primaryModels as $i => $primaryModel) {
+ if ($primaryModels[$i][$primaryName] instanceof ActiveRecordInterface) {
+ $primaryModels[$i][$primaryName]->populateRelation($name, $primaryModel);
+ } elseif (!empty($primaryModels[$i][$primaryName])) {
+ $primaryModels[$i][$primaryName][$name] = $primaryModel;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $models
+ * @param array $link
+ * @param array $viaModels
+ * @param array $viaLink
+ * @param boolean $checkMultiple
+ * @return array
+ */
+ private function buildBuckets($models, $link, $viaModels = null, $viaLink = null, $checkMultiple = true)
+ {
+ if ($viaModels !== null) {
+ $map = [];
+ $viaLinkKeys = array_keys($viaLink);
+ $linkValues = array_values($link);
+ foreach ($viaModels as $viaModel) {
+ $key1 = $this->getModelKey($viaModel, $viaLinkKeys);
+ $key2 = $this->getModelKey($viaModel, $linkValues);
+ $map[$key2][$key1] = true;
+ }
+ }
+
+ $buckets = [];
+ $linkKeys = array_keys($link);
+
+ if (isset($map)) {
+ foreach ($models as $i => $model) {
+ $key = $this->getModelKey($model, $linkKeys);
+ if (isset($map[$key])) {
+ foreach (array_keys($map[$key]) as $key2) {
+ if ($this->indexBy !== null) {
+ $buckets[$key2][$i] = $model;
+ } else {
+ $buckets[$key2][] = $model;
+ }
+ }
+ }
+ }
+ } else {
+ foreach ($models as $i => $model) {
+ $key = $this->getModelKey($model, $linkKeys);
+ if ($this->indexBy !== null) {
+ $buckets[$key][$i] = $model;
+ } else {
+ $buckets[$key][] = $model;
+ }
+ }
+ }
+
+ if ($checkMultiple && !$this->multiple) {
+ foreach ($buckets as $i => $bucket) {
+ $buckets[$i] = reset($bucket);
+ }
+ }
+
+ return $buckets;
+ }
+
+ /**
+ * @param array $models
+ */
+ private function filterByModels($models)
+ {
+ $attributes = array_keys($this->link);
+ $values = [];
+ if (count($attributes) === 1) {
+ // single key
+ $attribute = reset($this->link);
+ foreach ($models as $model) {
+ if (($value = $model[$attribute]) !== null) {
+ $values[] = $value;
+ }
+ }
+ } else {
+ // composite keys
+ foreach ($models as $model) {
+ $v = [];
+ foreach ($this->link as $attribute => $link) {
+ $v[$attribute] = $model[$link];
+ }
+ $values[] = $v;
+ }
+ }
+ $this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);
+ }
+
+ /**
+ * @param ActiveRecord|array $model
+ * @param array $attributes
+ * @return string
+ */
+ private function getModelKey($model, $attributes)
+ {
+ if (count($attributes) > 1) {
+ $key = [];
+ foreach ($attributes as $attribute) {
+ $key[] = $model[$attribute];
+ }
+
+ return serialize($key);
+ } else {
+ $attribute = reset($attributes);
+ $key = $model[$attribute];
+
+ return is_scalar($key) ? $key : serialize($key);
+ }
+ }
+
+ /**
+ * @param array $primaryModels either array of AR instances or arrays
+ * @return array
+ */
+ private function findPivotRows($primaryModels)
+ {
+ if (empty($primaryModels)) {
+ return [];
+ }
+ $this->filterByModels($primaryModels);
+ /** @var ActiveRecord $primaryModel */
+ $primaryModel = reset($primaryModels);
+ if (!$primaryModel instanceof ActiveRecordInterface) {
+ // when primaryModels are array of arrays (asArray case)
+ $primaryModel = new $this->modelClass;
+ }
+
+ return $this->asArray()->all($primaryModel->getDb());
+ }
}
diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php
index a1dd13e3179..b781a7ed104 100644
--- a/framework/db/BaseActiveRecord.php
+++ b/framework/db/BaseActiveRecord.php
@@ -40,1360 +40,1377 @@
*/
abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
{
- /**
- * @event Event an event that is triggered when the record is initialized via [[init()]].
- */
- const EVENT_INIT = 'init';
- /**
- * @event Event an event that is triggered after the record is created and populated with query result.
- */
- const EVENT_AFTER_FIND = 'afterFind';
- /**
- * @event ModelEvent an event that is triggered before inserting a record.
- * You may set [[ModelEvent::isValid]] to be false to stop the insertion.
- */
- const EVENT_BEFORE_INSERT = 'beforeInsert';
- /**
- * @event Event an event that is triggered after a record is inserted.
- */
- const EVENT_AFTER_INSERT = 'afterInsert';
- /**
- * @event ModelEvent an event that is triggered before updating a record.
- * You may set [[ModelEvent::isValid]] to be false to stop the update.
- */
- const EVENT_BEFORE_UPDATE = 'beforeUpdate';
- /**
- * @event Event an event that is triggered after a record is updated.
- */
- const EVENT_AFTER_UPDATE = 'afterUpdate';
- /**
- * @event ModelEvent an event that is triggered before deleting a record.
- * You may set [[ModelEvent::isValid]] to be false to stop the deletion.
- */
- const EVENT_BEFORE_DELETE = 'beforeDelete';
- /**
- * @event Event an event that is triggered after a record is deleted.
- */
- const EVENT_AFTER_DELETE = 'afterDelete';
-
- /**
- * @var array attribute values indexed by attribute names
- */
- private $_attributes = [];
- /**
- * @var array old attribute values indexed by attribute names.
- */
- private $_oldAttributes;
- /**
- * @var array related models indexed by the relation names
- */
- private $_related = [];
-
-
- /**
- * Creates an [[ActiveQuery]] instance for query purpose.
- *
- * The returned [[ActiveQuery]] instance can be further customized by calling
- * methods defined in [[ActiveQuery]] before `one()`, `all()` or `value()` is
- * called to return the populated active records:
- *
- * ~~~
- * // find all customers
- * $customers = Customer::find()->all();
- *
- * // find all active customers and order them by their age:
- * $customers = Customer::find()
- * ->where(['status' => 1])
- * ->orderBy('age')
- * ->all();
- *
- * // find a single customer whose primary key value is 10
- * $customer = Customer::find(10);
- *
- * // the above is equivalent to:
- * $customer = Customer::find()->where(['id' => 10])->one();
- *
- * // find a single customer whose age is 30 and whose status is 1
- * $customer = Customer::find(['age' => 30, 'status' => 1]);
- *
- * // the above is equivalent to:
- * $customer = Customer::find()->where(['age' => 30, 'status' => 1])->one();
- * ~~~
- *
- * @param mixed $q the query parameter. This can be one of the followings:
- *
- * - a scalar value (integer or string): query by a single primary key value and return the
- * corresponding record.
- * - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
- * - null: return a new [[ActiveQuery]] object for further query purpose.
- *
- * @return ActiveQuery|static|null When `$q` is null, a new [[ActiveQuery]] instance
- * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
- * returned (null will be returned if there is no matching).
- * @throws InvalidConfigException if the AR class does not have a primary key
- * @see createQuery()
- */
- public static function find($q = null)
- {
- $query = static::createQuery();
- if (is_array($q)) {
- return $query->andWhere($q)->one();
- } elseif ($q !== null) {
- // query by primary key
- $primaryKey = static::primaryKey();
- if (isset($primaryKey[0])) {
- return $query->andWhere([$primaryKey[0] => $q])->one();
- } else {
- throw new InvalidConfigException(get_called_class() . ' must have a primary key.');
- }
- }
- return $query;
- }
-
- /**
- * Updates the whole table using the provided attribute values and conditions.
- * For example, to change the status to be 1 for all customers whose status is 2:
- *
- * ~~~
- * Customer::updateAll(['status' => 1], 'status = 2');
- * ~~~
- *
- * @param array $attributes attribute values (name-value pairs) to be saved into the table
- * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @return integer the number of rows updated
- */
- public static function updateAll($attributes, $condition = '')
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported.');
- }
-
- /**
- * Updates the whole table using the provided counter changes and conditions.
- * For example, to increment all customers' age by 1,
- *
- * ~~~
- * Customer::updateAllCounters(['age' => 1]);
- * ~~~
- *
- * @param array $counters the counters to be updated (attribute name => increment value).
- * Use negative values if you want to decrement the counters.
- * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @return integer the number of rows updated
- */
- public static function updateAllCounters($counters, $condition = '')
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported.');
- }
-
- /**
- * Deletes rows in the table using the provided conditions.
- * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
- *
- * For example, to delete all customers whose status is 3:
- *
- * ~~~
- * Customer::deleteAll('status = 3');
- * ~~~
- *
- * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
- * Please refer to [[Query::where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return integer the number of rows deleted
- */
- public static function deleteAll($condition = '', $params = [])
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported.');
- }
-
- /**
- * Returns the name of the column that stores the lock version for implementing optimistic locking.
- *
- * Optimistic locking allows multiple users to access the same record for edits and avoids
- * potential conflicts. In case when a user attempts to save the record upon some staled data
- * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
- * and the update or deletion is skipped.
- *
- * Optimistic locking is only supported by [[update()]] and [[delete()]].
- *
- * To use Optimistic locking:
- *
- * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
- * Override this method to return the name of this column.
- * 2. In the Web form that collects the user input, add a hidden field that stores
- * the lock version of the recording being updated.
- * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
- * and implement necessary business logic (e.g. merging the changes, prompting stated data)
- * to resolve the conflict.
- *
- * @return string the column name that stores the lock version of a table row.
- * If null is returned (default implemented), optimistic locking will not be supported.
- */
- public function optimisticLock()
- {
- return null;
- }
-
- /**
- * PHP getter magic method.
- * This method is overridden so that attributes and related objects can be accessed like properties.
- *
- * @param string $name property name
- * @throws \yii\base\InvalidParamException if relation name is wrong
- * @return mixed property value
- * @see getAttribute()
- */
- public function __get($name)
- {
- if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
- return $this->_attributes[$name];
- } elseif ($this->hasAttribute($name)) {
- return null;
- } else {
- if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
- return $this->_related[$name];
- }
- $value = parent::__get($name);
- if ($value instanceof ActiveQueryInterface) {
- return $this->_related[$name] = $value->findFor($name, $this);
- } else {
- return $value;
- }
- }
- }
-
- /**
- * PHP setter magic method.
- * This method is overridden so that AR attributes can be accessed like properties.
- * @param string $name property name
- * @param mixed $value property value
- */
- public function __set($name, $value)
- {
- if ($this->hasAttribute($name)) {
- $this->_attributes[$name] = $value;
- } else {
- parent::__set($name, $value);
- }
- }
-
- /**
- * Checks if a property value is null.
- * This method overrides the parent implementation by checking if the named attribute is null or not.
- * @param string $name the property name or the event name
- * @return boolean whether the property value is null
- */
- public function __isset($name)
- {
- try {
- return $this->__get($name) !== null;
- } catch (\Exception $e) {
- return false;
- }
- }
-
- /**
- * Sets a component property to be null.
- * This method overrides the parent implementation by clearing
- * the specified attribute value.
- * @param string $name the property name or the event name
- */
- public function __unset($name)
- {
- if ($this->hasAttribute($name)) {
- unset($this->_attributes[$name]);
- } elseif (array_key_exists($name, $this->_related)) {
- unset($this->_related[$name]);
- } elseif ($this->getRelation($name, false) === null) {
- parent::__unset($name);
- }
- }
-
- /**
- * Declares a `has-one` relation.
- * The declaration is returned in terms of a relational [[ActiveQuery]] instance
- * through which the related record can be queried and retrieved back.
- *
- * A `has-one` relation means that there is at most one related record matching
- * the criteria set by this relation, e.g., a customer has one country.
- *
- * For example, to declare the `country` relation for `Customer` class, we can write
- * the following code in the `Customer` class:
- *
- * ~~~
- * public function getCountry()
- * {
- * return $this->hasOne(Country::className(), ['id' => 'country_id']);
- * }
- * ~~~
- *
- * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
- * in the related class `Country`, while the 'country_id' value refers to an attribute name
- * in the current AR class.
- *
- * Call methods declared in [[ActiveQuery]] to further customize the relation.
- *
- * @param string $class the class name of the related record
- * @param array $link the primary-foreign key constraint. The keys of the array refer to
- * the attributes of the record associated with the `$class` model, while the values of the
- * array refer to the corresponding attributes in **this** AR class.
- * @return ActiveQueryInterface the relational query object.
- */
- public function hasOne($class, $link)
- {
- /** @var ActiveRecordInterface $class */
- return $class::createQuery([
- 'modelClass' => $class,
- 'primaryModel' => $this,
- 'link' => $link,
- 'multiple' => false,
- ]);
- }
-
- /**
- * Declares a `has-many` relation.
- * The declaration is returned in terms of a relational [[ActiveQuery]] instance
- * through which the related record can be queried and retrieved back.
- *
- * A `has-many` relation means that there are multiple related records matching
- * the criteria set by this relation, e.g., a customer has many orders.
- *
- * For example, to declare the `orders` relation for `Customer` class, we can write
- * the following code in the `Customer` class:
- *
- * ~~~
- * public function getOrders()
- * {
- * return $this->hasMany(Order::className(), ['customer_id' => 'id']);
- * }
- * ~~~
- *
- * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
- * an attribute name in the related class `Order`, while the 'id' value refers to
- * an attribute name in the current AR class.
- *
- * Call methods declared in [[ActiveQuery]] to further customize the relation.
- *
- * @param string $class the class name of the related record
- * @param array $link the primary-foreign key constraint. The keys of the array refer to
- * the attributes of the record associated with the `$class` model, while the values of the
- * array refer to the corresponding attributes in **this** AR class.
- * @return ActiveQueryInterface the relational query object.
- */
- public function hasMany($class, $link)
- {
- /** @var ActiveRecordInterface $class */
- return $class::createQuery([
- 'modelClass' => $class,
- 'primaryModel' => $this,
- 'link' => $link,
- 'multiple' => true,
- ]);
- }
-
- /**
- * Populates the named relation with the related records.
- * Note that this method does not check if the relation exists or not.
- * @param string $name the relation name (case-sensitive)
- * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation.
- */
- public function populateRelation($name, $records)
- {
- $this->_related[$name] = $records;
- }
-
- /**
- * Check whether the named relation has been populated with records.
- * @param string $name the relation name (case-sensitive)
- * @return boolean whether relation has been populated with records.
- */
- public function isRelationPopulated($name)
- {
- return array_key_exists($name, $this->_related);
- }
-
- /**
- * Returns all populated related records.
- * @return array an array of related records indexed by relation names.
- */
- public function getRelatedRecords()
- {
- return $this->_related;
- }
-
- /**
- * Returns a value indicating whether the model has an attribute with the specified name.
- * @param string $name the name of the attribute
- * @return boolean whether the model has an attribute with the specified name.
- */
- public function hasAttribute($name)
- {
- return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
- }
-
- /**
- * Returns the named attribute value.
- * If this record is the result of a query and the attribute is not loaded,
- * null will be returned.
- * @param string $name the attribute name
- * @return mixed the attribute value. Null if the attribute is not set or does not exist.
- * @see hasAttribute()
- */
- public function getAttribute($name)
- {
- return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
- }
-
- /**
- * Sets the named attribute value.
- * @param string $name the attribute name
- * @param mixed $value the attribute value.
- * @throws InvalidParamException if the named attribute does not exist.
- * @see hasAttribute()
- */
- public function setAttribute($name, $value)
- {
- if ($this->hasAttribute($name)) {
- $this->_attributes[$name] = $value;
- } else {
- throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
- }
- }
-
- /**
- * Returns the old attribute values.
- * @return array the old attribute values (name-value pairs)
- */
- public function getOldAttributes()
- {
- return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
- }
-
- /**
- * Sets the old attribute values.
- * All existing old attribute values will be discarded.
- * @param array $values old attribute values to be set.
- */
- public function setOldAttributes($values)
- {
- $this->_oldAttributes = $values;
- }
-
- /**
- * Returns the old value of the named attribute.
- * If this record is the result of a query and the attribute is not loaded,
- * null will be returned.
- * @param string $name the attribute name
- * @return mixed the old attribute value. Null if the attribute is not loaded before
- * or does not exist.
- * @see hasAttribute()
- */
- public function getOldAttribute($name)
- {
- return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
- }
-
- /**
- * Sets the old value of the named attribute.
- * @param string $name the attribute name
- * @param mixed $value the old attribute value.
- * @throws InvalidParamException if the named attribute does not exist.
- * @see hasAttribute()
- */
- public function setOldAttribute($name, $value)
- {
- if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
- $this->_oldAttributes[$name] = $value;
- } else {
- throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
- }
- }
-
- /**
- * Marks an attribute dirty.
- * This method may be called to force updating a record when calling [[update()]],
- * even if there is no change being made to the record.
- * @param string $name the attribute name
- */
- public function markAttributeDirty($name)
- {
- unset($this->_oldAttributes[$name]);
- }
-
- /**
- * Returns a value indicating whether the named attribute has been changed.
- * @param string $name the name of the attribute
- * @return boolean whether the attribute has been changed
- */
- public function isAttributeChanged($name)
- {
- if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
- return $this->_attributes[$name] !== $this->_oldAttributes[$name];
- } else {
- return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
- }
- }
-
- /**
- * Returns the attribute values that have been modified since they are loaded or saved most recently.
- * @param string[]|null $names the names of the attributes whose values may be returned if they are
- * changed recently. If null, [[attributes()]] will be used.
- * @return array the changed attribute values (name-value pairs)
- */
- public function getDirtyAttributes($names = null)
- {
- if ($names === null) {
- $names = $this->attributes();
- }
- $names = array_flip($names);
- $attributes = [];
- if ($this->_oldAttributes === null) {
- foreach ($this->_attributes as $name => $value) {
- if (isset($names[$name])) {
- $attributes[$name] = $value;
- }
- }
- } else {
- foreach ($this->_attributes as $name => $value) {
- if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
- $attributes[$name] = $value;
- }
- }
- }
- return $attributes;
- }
-
- /**
- * Saves the current record.
- *
- * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
- * when [[isNewRecord]] is false.
- *
- * For example, to save a customer record:
- *
- * ~~~
- * $customer = new Customer; // or $customer = Customer::find($id);
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->save();
- * ~~~
- *
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be saved to database.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return boolean whether the saving succeeds
- */
- public function save($runValidation = true, $attributes = null)
- {
- if ($this->getIsNewRecord()) {
- return $this->insert($runValidation, $attributes);
- } else {
- return $this->update($runValidation, $attributes) !== false;
- }
- }
-
- /**
- * Saves the changes to this active record into the associated database table.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
- * fails, it will skip the rest of the steps;
- * 2. call [[afterValidate()]] when `$runValidation` is true.
- * 3. call [[beforeSave()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 4. save the record into database. If this fails, it will skip the rest of the steps;
- * 5. call [[afterSave()]];
- *
- * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
- * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
- * will be raised by the corresponding methods.
- *
- * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
- *
- * For example, to update a customer record:
- *
- * ~~~
- * $customer = Customer::find($id);
- * $customer->name = $name;
- * $customer->email = $email;
- * $customer->update();
- * ~~~
- *
- * Note that it is possible the update does not affect any row in the table.
- * In this case, this method will return 0. For this reason, you should use the following
- * code to check if update() is successful or not:
- *
- * ~~~
- * if ($this->update() !== false) {
- * // update successful
- * } else {
- * // update failed
- * }
- * ~~~
- *
- * @param boolean $runValidation whether to perform validation before saving the record.
- * If the validation fails, the record will not be inserted into the database.
- * @param array $attributes list of attributes that need to be saved. Defaults to null,
- * meaning all attributes that are loaded from DB will be saved.
- * @return integer|boolean the number of rows affected, or false if validation fails
- * or [[beforeSave()]] stops the updating process.
- * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
- * being updated is outdated.
- * @throws \Exception in case update failed.
- */
- public function update($runValidation = true, $attributes = null)
- {
- if ($runValidation && !$this->validate($attributes)) {
- return false;
- }
- return $this->updateInternal($attributes);
- }
-
- /**
- * Updates the specified attributes.
- *
- * This method is a shortcut to [[update()]] when data validation is not needed
- * and only a list of attributes need to be updated.
- *
- * You may specify the attributes to be updated as name list or name-value pairs.
- * If the latter, the corresponding attribute values will be modified accordingly.
- * The method will then save the specified attributes into database.
- *
- * Note that this method will NOT perform data validation.
- *
- * @param array $attributes the attributes (names or name-value pairs) to be updated
- * @return integer|boolean the number of rows affected, or false if [[beforeSave()]] stops the updating process.
- */
- public function updateAttributes($attributes)
- {
- $attrs = [];
- foreach ($attributes as $name => $value) {
- if (is_integer($name)) {
- $attrs[] = $value;
- } else {
- $this->$name = $value;
- $attrs[] = $name;
- }
- }
- return $this->update(false, $attrs);
- }
-
- /**
- * @see update()
- * @throws StaleObjectException
- */
- protected function updateInternal($attributes = null)
- {
- if (!$this->beforeSave(false)) {
- return false;
- }
- $values = $this->getDirtyAttributes($attributes);
- if (empty($values)) {
- $this->afterSave(false);
- return 0;
- }
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- if (!isset($values[$lock])) {
- $values[$lock] = $this->$lock + 1;
- }
- $condition[$lock] = $this->$lock;
- }
- // We do not check the return value of updateAll() because it's possible
- // that the UPDATE statement doesn't change anything and thus returns 0.
- $rows = $this->updateAll($values, $condition);
-
- if ($lock !== null && !$rows) {
- throw new StaleObjectException('The object being updated is outdated.');
- }
-
- foreach ($values as $name => $value) {
- $this->_oldAttributes[$name] = $this->_attributes[$name];
- }
- $this->afterSave(false);
- return $rows;
- }
-
- /**
- * Updates one or several counter columns for the current AR object.
- * Note that this method differs from [[updateAllCounters()]] in that it only
- * saves counters for the current AR object.
- *
- * An example usage is as follows:
- *
- * ~~~
- * $post = Post::find($id);
- * $post->updateCounters(['view_count' => 1]);
- * ~~~
- *
- * @param array $counters the counters to be updated (attribute name => increment value)
- * Use negative values if you want to decrement the counters.
- * @return boolean whether the saving is successful
- * @see updateAllCounters()
- */
- public function updateCounters($counters)
- {
- if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
- foreach ($counters as $name => $value) {
- $this->_attributes[$name] += $value;
- $this->_oldAttributes[$name] = $this->_attributes[$name];
- }
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Deletes the table row corresponding to this active record.
- *
- * This method performs the following steps in order:
- *
- * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
- * rest of the steps;
- * 2. delete the record from the database;
- * 3. call [[afterDelete()]].
- *
- * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
- * will be raised by the corresponding methods.
- *
- * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
- * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
- * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
- * being deleted is outdated.
- * @throws \Exception in case delete failed.
- */
- public function delete()
- {
- $result = false;
- if ($this->beforeDelete()) {
- // we do not check the return value of deleteAll() because it's possible
- // the record is already deleted in the database and thus the method will return 0
- $condition = $this->getOldPrimaryKey(true);
- $lock = $this->optimisticLock();
- if ($lock !== null) {
- $condition[$lock] = $this->$lock;
- }
- $result = $this->deleteAll($condition);
- if ($lock !== null && !$result) {
- throw new StaleObjectException('The object being deleted is outdated.');
- }
- $this->_oldAttributes = null;
- $this->afterDelete();
- }
- return $result;
- }
-
- /**
- * Returns a value indicating whether the current record is new.
- * @return boolean whether the record is new and should be inserted when calling [[save()]].
- */
- public function getIsNewRecord()
- {
- return $this->_oldAttributes === null;
- }
-
- /**
- * Sets the value indicating whether the record is new.
- * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
- * @see getIsNewRecord()
- */
- public function setIsNewRecord($value)
- {
- $this->_oldAttributes = $value ? null : $this->_attributes;
- }
-
- /**
- * Initializes the object.
- * This method is called at the end of the constructor.
- * The default implementation will trigger an [[EVENT_INIT]] event.
- * If you override this method, make sure you call the parent implementation at the end
- * to ensure triggering of the event.
- */
- public function init()
- {
- parent::init();
- $this->trigger(self::EVENT_INIT);
- }
-
- /**
- * This method is called when the AR object is created and populated with the query result.
- * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
- * When overriding this method, make sure you call the parent implementation to ensure the
- * event is triggered.
- */
- public function afterFind()
- {
- $this->trigger(self::EVENT_AFTER_FIND);
- }
-
- /**
- * This method is called at the beginning of inserting or updating a record.
- * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
- * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
- * When overriding this method, make sure you call the parent implementation like the following:
- *
- * ~~~
- * public function beforeSave($insert)
- * {
- * if (parent::beforeSave($insert)) {
- * // ...custom code here...
- * return true;
- * } else {
- * return false;
- * }
- * }
- * ~~~
- *
- * @param boolean $insert whether this method called while inserting a record.
- * If false, it means the method is called while updating a record.
- * @return boolean whether the insertion or updating should continue.
- * If false, the insertion or updating will be cancelled.
- */
- public function beforeSave($insert)
- {
- $event = new ModelEvent;
- $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
- return $event->isValid;
- }
-
- /**
- * This method is called at the end of inserting or updating a record.
- * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
- * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false.
- * When overriding this method, make sure you call the parent implementation so that
- * the event is triggered.
- * @param boolean $insert whether this method called while inserting a record.
- * If false, it means the method is called while updating a record.
- */
- public function afterSave($insert)
- {
- $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE);
- }
-
- /**
- * This method is invoked before deleting a record.
- * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
- * When overriding this method, make sure you call the parent implementation like the following:
- *
- * ~~~
- * public function beforeDelete()
- * {
- * if (parent::beforeDelete()) {
- * // ...custom code here...
- * return true;
- * } else {
- * return false;
- * }
- * }
- * ~~~
- *
- * @return boolean whether the record should be deleted. Defaults to true.
- */
- public function beforeDelete()
- {
- $event = new ModelEvent;
- $this->trigger(self::EVENT_BEFORE_DELETE, $event);
- return $event->isValid;
- }
-
- /**
- * This method is invoked after deleting a record.
- * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
- * You may override this method to do postprocessing after the record is deleted.
- * Make sure you call the parent implementation so that the event is raised properly.
- */
- public function afterDelete()
- {
- $this->trigger(self::EVENT_AFTER_DELETE);
- }
-
- /**
- * Repopulates this active record with the latest data.
- * @return boolean whether the row still exists in the database. If true, the latest data
- * will be populated to this active record. Otherwise, this record will remain unchanged.
- */
- public function refresh()
- {
- $record = $this->find($this->getPrimaryKey(true));
- if ($record === null) {
- return false;
- }
- foreach ($this->attributes() as $name) {
- $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
- }
- $this->_oldAttributes = $this->_attributes;
- $this->_related = [];
- return true;
- }
-
- /**
- * Returns a value indicating whether the given active record is the same as the current one.
- * The comparison is made by comparing the table names and the primary key values of the two active records.
- * If one of the records [[isNewRecord|is new]] they are also considered not equal.
- * @param ActiveRecordInterface $record record to compare to
- * @return boolean whether the two active records refer to the same row in the same database table.
- */
- public function equals($record)
- {
- if ($this->getIsNewRecord() || $record->getIsNewRecord()) {
- return false;
- }
- return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey();
- }
-
- /**
- * Returns the primary key value(s).
- * @param boolean $asArray whether to return the primary key value as an array. If true,
- * the return value will be an array with column names as keys and column values as values.
- * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
- * @property mixed The primary key value. An array (column name => column value) is returned if
- * the primary key is composite. A string is returned otherwise (null will be returned if
- * the key value is null).
- * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
- * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
- * the key value is null).
- */
- public function getPrimaryKey($asArray = false)
- {
- $keys = $this->primaryKey();
- if (count($keys) === 1 && !$asArray) {
- return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
- } else {
- $values = [];
- foreach ($keys as $name) {
- $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
- }
- return $values;
- }
- }
-
- /**
- * Returns the old primary key value(s).
- * This refers to the primary key value that is populated into the record
- * after executing a find method (e.g. find(), findAll()).
- * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
- * @param boolean $asArray whether to return the primary key value as an array. If true,
- * the return value will be an array with column name as key and column value as value.
- * If this is false (default), a scalar value will be returned for non-composite primary key.
- * @property mixed The old primary key value. An array (column name => column value) is
- * returned if the primary key is composite. A string is returned otherwise (null will be
- * returned if the key value is null).
- * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
- * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
- * the key value is null).
- */
- public function getOldPrimaryKey($asArray = false)
- {
- $keys = $this->primaryKey();
- if (count($keys) === 1 && !$asArray) {
- return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
- } else {
- $values = [];
- foreach ($keys as $name) {
- $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
- }
- return $values;
- }
- }
-
- /**
- * Populates an active record object using a row of data from the database/storage.
- *
- * This is an internal method meant to be called to create active record objects after
- * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
- * the query results into active records.
- *
- * When calling this method manually you should call [[afterFind()]] on the created
- * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
- *
- * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
- * created by [[instantiate()]] beforehand.
- * @param array $row attribute values (name => value)
- */
- public static function populateRecord($record, $row)
- {
- $columns = array_flip($record->attributes());
- foreach ($row as $name => $value) {
- if (isset($columns[$name])) {
- $record->_attributes[$name] = $value;
- } else {
- $record->$name = $value;
- }
- }
- $record->_oldAttributes = $record->_attributes;
- }
-
- /**
- * Creates an active record instance.
- *
- * This method is called together with [[populateRecord()]] by [[ActiveQuery]].
- * It is not meant to be used for creating new records directly.
- *
- * You may override this method if the instance being created
- * depends on the row data to be populated into the record.
- * For example, by creating a record based on the value of a column,
- * you may implement the so-called single-table inheritance mapping.
- * @param array $row row data to be populated into the record.
- * @return static the newly created active record
- */
- public static function instantiate($row)
- {
- return new static;
- }
-
- /**
- * Returns whether there is an element at the specified offset.
- * This method is required by the interface ArrayAccess.
- * @param mixed $offset the offset to check on
- * @return boolean whether there is an element at the specified offset.
- */
- public function offsetExists($offset)
- {
- return $this->__isset($offset);
- }
-
- /**
- * Returns the relation object with the specified name.
- * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object.
- * It can be declared in either the Active Record class itself or one of its behaviors.
- * @param string $name the relation name
- * @param boolean $throwException whether to throw exception if the relation does not exist.
- * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist
- * and `$throwException` is false, null will be returned.
- * @throws InvalidParamException if the named relation does not exist.
- */
- public function getRelation($name, $throwException = true)
- {
- $getter = 'get' . $name;
- try {
- // the relation could be defined in a behavior
- $relation = $this->$getter();
- } catch (UnknownMethodException $e) {
- if ($throwException) {
- throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
- } else {
- return null;
- }
- }
- if (!$relation instanceof ActiveQueryInterface) {
- if ($throwException) {
- throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
- } else {
- return null;
- }
- }
-
- if (method_exists($this, $getter)) {
- // relation name is case sensitive, trying to validate it when the relation is defined within this class
- $method = new \ReflectionMethod($this, $getter);
- $realName = lcfirst(substr($method->getName(), 3));
- if ($realName !== $name) {
- if ($throwException) {
- throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\".");
- } else {
- return null;
- }
- }
- }
-
- return $relation;
- }
-
- /**
- * Establishes the relationship between two models.
- *
- * The relationship is established by setting the foreign key value(s) in one model
- * to be the corresponding primary key value(s) in the other model.
- * The model with the foreign key will be saved into database without performing validation.
- *
- * If the relationship involves a pivot table, a new row will be inserted into the
- * pivot table which contains the primary key values from both models.
- *
- * Note that this method requires that the primary key value is not null.
- *
- * @param string $name the case sensitive name of the relationship
- * @param ActiveRecordInterface $model the model to be linked with the current one.
- * @param array $extraColumns additional column values to be saved into the pivot table.
- * This parameter is only meaningful for a relationship involving a pivot table
- * (i.e., a relation set with [[ActiveRelationTrait::via()]] or `[[ActiveQuery::viaTable()]]`.)
- * @throws InvalidCallException if the method is unable to link two models.
- */
- public function link($name, $model, $extraColumns = [])
- {
- $relation = $this->getRelation($name);
-
- if ($relation->via !== null) {
- if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
- throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
- }
- if (is_array($relation->via)) {
- /** @var ActiveQuery $viaRelation */
- list($viaName, $viaRelation) = $relation->via;
- $viaClass = $viaRelation->modelClass;
- // unset $viaName so that it can be reloaded to reflect the change
- unset($this->_related[$viaName]);
- } else {
- $viaRelation = $relation->via;
- $viaTable = reset($relation->via->from);
- }
- $columns = [];
- foreach ($viaRelation->link as $a => $b) {
- $columns[$a] = $this->$b;
- }
- foreach ($relation->link as $a => $b) {
- $columns[$b] = $model->$a;
- }
- foreach ($extraColumns as $k => $v) {
- $columns[$k] = $v;
- }
- if (is_array($relation->via)) {
- /** @var $viaClass ActiveRecordInterface */
- /** @var $record ActiveRecordInterface */
- $record = new $viaClass();
- foreach ($columns as $column => $value) {
- $record->$column = $value;
- }
- $record->insert(false);
- } else {
- /** @var $viaTable string */
- static::getDb()->createCommand()
- ->insert($viaTable, $columns)->execute();
- }
- } else {
- $p1 = $model->isPrimaryKey(array_keys($relation->link));
- $p2 = $this->isPrimaryKey(array_values($relation->link));
- if ($p1 && $p2) {
- if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
- throw new InvalidCallException('Unable to link models: both models are newly created.');
- } elseif ($this->getIsNewRecord()) {
- $this->bindModels(array_flip($relation->link), $this, $model);
- } else {
- $this->bindModels($relation->link, $model, $this);
- }
- } elseif ($p1) {
- $this->bindModels(array_flip($relation->link), $this, $model);
- } elseif ($p2) {
- $this->bindModels($relation->link, $model, $this);
- } else {
- throw new InvalidCallException('Unable to link models: the link does not involve any primary key.');
- }
- }
-
- // update lazily loaded related objects
- if (!$relation->multiple) {
- $this->_related[$name] = $model;
- } elseif (isset($this->_related[$name])) {
- if ($relation->indexBy !== null) {
- $indexBy = $relation->indexBy;
- $this->_related[$name][$model->$indexBy] = $model;
- } else {
- $this->_related[$name][] = $model;
- }
- }
- }
-
- /**
- * Destroys the relationship between two models.
- *
- * The model with the foreign key of the relationship will be deleted if `$delete` is true.
- * Otherwise, the foreign key will be set null and the model will be saved without validation.
- *
- * @param string $name the case sensitive name of the relationship.
- * @param ActiveRecordInterface $model the model to be unlinked from the current one.
- * @param boolean $delete whether to delete the model that contains the foreign key.
- * If false, the model's foreign key will be set null and saved.
- * If true, the model containing the foreign key will be deleted.
- * @throws InvalidCallException if the models cannot be unlinked
- */
- public function unlink($name, $model, $delete = false)
- {
- $relation = $this->getRelation($name);
-
- if ($relation->via !== null) {
- if (is_array($relation->via)) {
- /** @var ActiveQuery $viaRelation */
- list($viaName, $viaRelation) = $relation->via;
- $viaClass = $viaRelation->modelClass;
- unset($this->_related[$viaName]);
- } else {
- $viaRelation = $relation->via;
- $viaTable = reset($relation->via->from);
- }
- $columns = [];
- foreach ($viaRelation->link as $a => $b) {
- $columns[$a] = $this->$b;
- }
- foreach ($relation->link as $a => $b) {
- $columns[$b] = $model->$a;
- }
- if (is_array($relation->via)) {
- /** @var $viaClass ActiveRecordInterface */
- if ($delete) {
- $viaClass::deleteAll($columns);
- } else {
- $nulls = [];
- foreach (array_keys($columns) as $a) {
- $nulls[$a] = null;
- }
- $viaClass::updateAll($nulls, $columns);
- }
- } else {
- /** @var $viaTable string */
- /** @var Command $command */
- $command = static::getDb()->createCommand();
- if ($delete) {
- $command->delete($viaTable, $columns)->execute();
- } else {
- $nulls = [];
- foreach (array_keys($columns) as $a) {
- $nulls[$a] = null;
- }
- $command->update($viaTable, $nulls, $columns)->execute();
- }
- }
- } else {
- $p1 = $model->isPrimaryKey(array_keys($relation->link));
- $p2 = $this->isPrimaryKey(array_values($relation->link));
- if ($p1 && $p2 || $p2) {
- foreach ($relation->link as $a => $b) {
- $model->$a = null;
- }
- $delete ? $model->delete() : $model->save(false);
- } elseif ($p1) {
- foreach ($relation->link as $b) {
- $this->$b = null;
- }
- $delete ? $this->delete() : $this->save(false);
- } else {
- throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
- }
- }
-
- if (!$relation->multiple) {
- unset($this->_related[$name]);
- } elseif (isset($this->_related[$name])) {
- /** @var ActiveRecordInterface $b */
- foreach ($this->_related[$name] as $a => $b) {
- if ($model->getPrimaryKey() == $b->getPrimaryKey()) {
- unset($this->_related[$name][$a]);
- }
- }
- }
- }
-
- /**
- * @param array $link
- * @param BaseActiveRecord $foreignModel
- * @param BaseActiveRecord $primaryModel
- * @throws InvalidCallException
- */
- private function bindModels($link, $foreignModel, $primaryModel)
- {
- foreach ($link as $fk => $pk) {
- $value = $primaryModel->$pk;
- if ($value === null) {
- throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
- }
- $foreignModel->$fk = $value;
- }
- $foreignModel->save(false);
- }
-
- /**
- * Returns a value indicating whether the given set of attributes represents the primary key for this model
- * @param array $keys the set of attributes to check
- * @return boolean whether the given set of attributes represents the primary key for this model
- */
- public static function isPrimaryKey($keys)
- {
- $pks = static::primaryKey();
- if (count($keys) === count($pks)) {
- return count(array_intersect($keys, $pks)) === count($pks);
- } else {
- return false;
- }
- }
-
- /**
- * Returns the text label for the specified attribute.
- * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
- * @param string $attribute the attribute name
- * @return string the attribute label
- * @see generateAttributeLabel()
- * @see attributeLabels()
- */
- public function getAttributeLabel($attribute)
- {
- $labels = $this->attributeLabels();
- if (isset($labels[$attribute])) {
- return ($labels[$attribute]);
- } elseif (strpos($attribute, '.')) {
- $attributeParts = explode('.', $attribute);
- $neededAttribute = array_pop($attributeParts);
-
- $relatedModel = $this;
- foreach ($attributeParts as $relationName) {
- if (isset($this->_related[$relationName]) && $this->_related[$relationName] instanceof self) {
- $relatedModel = $this->_related[$relationName];
- } else {
- try {
- $relation = $relatedModel->getRelation($relationName);
- } catch (InvalidParamException $e) {
- return $this->generateAttributeLabel($attribute);
- }
- $relatedModel = new $relation->modelClass;
- }
- }
-
- $labels = $relatedModel->attributeLabels();
- if (isset($labels[$neededAttribute])) {
- return $labels[$neededAttribute];
- }
- }
-
- return $this->generateAttributeLabel($attribute);
- }
-
- /**
- * @inheritdoc
- *
- * The default implementation returns the names of the columns whose values have been populated into this record.
- */
- public function fields()
- {
- $fields = array_keys($this->_attributes);
- return array_combine($fields, $fields);
- }
-
- /**
- * @inheritdoc
- *
- * The default implementation returns the names of the relations that have been populated into this record.
- */
- public function extraFields()
- {
- $fields = array_keys($this->getRelatedRecords());
- return array_combine($fields, $fields);
- }
-
- /**
- * Sets the element value at the specified offset to null.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `unset($model[$offset])`.
- * @param mixed $offset the offset to unset element
- */
- public function offsetUnset($offset)
- {
- if (property_exists($this, $offset)) {
- $this->$offset = null;
- } else {
- unset($this->$offset);
- }
- }
+ /**
+ * @event Event an event that is triggered when the record is initialized via [[init()]].
+ */
+ const EVENT_INIT = 'init';
+ /**
+ * @event Event an event that is triggered after the record is created and populated with query result.
+ */
+ const EVENT_AFTER_FIND = 'afterFind';
+ /**
+ * @event ModelEvent an event that is triggered before inserting a record.
+ * You may set [[ModelEvent::isValid]] to be false to stop the insertion.
+ */
+ const EVENT_BEFORE_INSERT = 'beforeInsert';
+ /**
+ * @event Event an event that is triggered after a record is inserted.
+ */
+ const EVENT_AFTER_INSERT = 'afterInsert';
+ /**
+ * @event ModelEvent an event that is triggered before updating a record.
+ * You may set [[ModelEvent::isValid]] to be false to stop the update.
+ */
+ const EVENT_BEFORE_UPDATE = 'beforeUpdate';
+ /**
+ * @event Event an event that is triggered after a record is updated.
+ */
+ const EVENT_AFTER_UPDATE = 'afterUpdate';
+ /**
+ * @event ModelEvent an event that is triggered before deleting a record.
+ * You may set [[ModelEvent::isValid]] to be false to stop the deletion.
+ */
+ const EVENT_BEFORE_DELETE = 'beforeDelete';
+ /**
+ * @event Event an event that is triggered after a record is deleted.
+ */
+ const EVENT_AFTER_DELETE = 'afterDelete';
+
+ /**
+ * @var array attribute values indexed by attribute names
+ */
+ private $_attributes = [];
+ /**
+ * @var array old attribute values indexed by attribute names.
+ */
+ private $_oldAttributes;
+ /**
+ * @var array related models indexed by the relation names
+ */
+ private $_related = [];
+
+ /**
+ * Creates an [[ActiveQuery]] instance for query purpose.
+ *
+ * The returned [[ActiveQuery]] instance can be further customized by calling
+ * methods defined in [[ActiveQuery]] before `one()`, `all()` or `value()` is
+ * called to return the populated active records:
+ *
+ * ~~~
+ * // find all customers
+ * $customers = Customer::find()->all();
+ *
+ * // find all active customers and order them by their age:
+ * $customers = Customer::find()
+ * ->where(['status' => 1])
+ * ->orderBy('age')
+ * ->all();
+ *
+ * // find a single customer whose primary key value is 10
+ * $customer = Customer::find(10);
+ *
+ * // the above is equivalent to:
+ * $customer = Customer::find()->where(['id' => 10])->one();
+ *
+ * // find a single customer whose age is 30 and whose status is 1
+ * $customer = Customer::find(['age' => 30, 'status' => 1]);
+ *
+ * // the above is equivalent to:
+ * $customer = Customer::find()->where(['age' => 30, 'status' => 1])->one();
+ * ~~~
+ *
+ * @param mixed $q the query parameter. This can be one of the followings:
+ *
+ * - a scalar value (integer or string): query by a single primary key value and return the
+ * corresponding record.
+ * - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
+ * - null: return a new [[ActiveQuery]] object for further query purpose.
+ *
+ * @return ActiveQuery|static|null When `$q` is null, a new [[ActiveQuery]] instance
+ * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
+ * returned (null will be returned if there is no matching).
+ * @throws InvalidConfigException if the AR class does not have a primary key
+ * @see createQuery()
+ */
+ public static function find($q = null)
+ {
+ $query = static::createQuery();
+ if (is_array($q)) {
+ return $query->andWhere($q)->one();
+ } elseif ($q !== null) {
+ // query by primary key
+ $primaryKey = static::primaryKey();
+ if (isset($primaryKey[0])) {
+ return $query->andWhere([$primaryKey[0] => $q])->one();
+ } else {
+ throw new InvalidConfigException(get_called_class() . ' must have a primary key.');
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Updates the whole table using the provided attribute values and conditions.
+ * For example, to change the status to be 1 for all customers whose status is 2:
+ *
+ * ~~~
+ * Customer::updateAll(['status' => 1], 'status = 2');
+ * ~~~
+ *
+ * @param array $attributes attribute values (name-value pairs) to be saved into the table
+ * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @return integer the number of rows updated
+ */
+ public static function updateAll($attributes, $condition = '')
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported.');
+ }
+
+ /**
+ * Updates the whole table using the provided counter changes and conditions.
+ * For example, to increment all customers' age by 1,
+ *
+ * ~~~
+ * Customer::updateAllCounters(['age' => 1]);
+ * ~~~
+ *
+ * @param array $counters the counters to be updated (attribute name => increment value).
+ * Use negative values if you want to decrement the counters.
+ * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @return integer the number of rows updated
+ */
+ public static function updateAllCounters($counters, $condition = '')
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported.');
+ }
+
+ /**
+ * Deletes rows in the table using the provided conditions.
+ * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
+ *
+ * For example, to delete all customers whose status is 3:
+ *
+ * ~~~
+ * Customer::deleteAll('status = 3');
+ * ~~~
+ *
+ * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return integer the number of rows deleted
+ */
+ public static function deleteAll($condition = '', $params = [])
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported.');
+ }
+
+ /**
+ * Returns the name of the column that stores the lock version for implementing optimistic locking.
+ *
+ * Optimistic locking allows multiple users to access the same record for edits and avoids
+ * potential conflicts. In case when a user attempts to save the record upon some staled data
+ * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
+ * and the update or deletion is skipped.
+ *
+ * Optimistic locking is only supported by [[update()]] and [[delete()]].
+ *
+ * To use Optimistic locking:
+ *
+ * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
+ * Override this method to return the name of this column.
+ * 2. In the Web form that collects the user input, add a hidden field that stores
+ * the lock version of the recording being updated.
+ * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
+ * and implement necessary business logic (e.g. merging the changes, prompting stated data)
+ * to resolve the conflict.
+ *
+ * @return string the column name that stores the lock version of a table row.
+ * If null is returned (default implemented), optimistic locking will not be supported.
+ */
+ public function optimisticLock()
+ {
+ return null;
+ }
+
+ /**
+ * PHP getter magic method.
+ * This method is overridden so that attributes and related objects can be accessed like properties.
+ *
+ * @param string $name property name
+ * @throws \yii\base\InvalidParamException if relation name is wrong
+ * @return mixed property value
+ * @see getAttribute()
+ */
+ public function __get($name)
+ {
+ if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
+ return $this->_attributes[$name];
+ } elseif ($this->hasAttribute($name)) {
+ return null;
+ } else {
+ if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
+ return $this->_related[$name];
+ }
+ $value = parent::__get($name);
+ if ($value instanceof ActiveQueryInterface) {
+ return $this->_related[$name] = $value->findFor($name, $this);
+ } else {
+ return $value;
+ }
+ }
+ }
+
+ /**
+ * PHP setter magic method.
+ * This method is overridden so that AR attributes can be accessed like properties.
+ * @param string $name property name
+ * @param mixed $value property value
+ */
+ public function __set($name, $value)
+ {
+ if ($this->hasAttribute($name)) {
+ $this->_attributes[$name] = $value;
+ } else {
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Checks if a property value is null.
+ * This method overrides the parent implementation by checking if the named attribute is null or not.
+ * @param string $name the property name or the event name
+ * @return boolean whether the property value is null
+ */
+ public function __isset($name)
+ {
+ try {
+ return $this->__get($name) !== null;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Sets a component property to be null.
+ * This method overrides the parent implementation by clearing
+ * the specified attribute value.
+ * @param string $name the property name or the event name
+ */
+ public function __unset($name)
+ {
+ if ($this->hasAttribute($name)) {
+ unset($this->_attributes[$name]);
+ } elseif (array_key_exists($name, $this->_related)) {
+ unset($this->_related[$name]);
+ } elseif ($this->getRelation($name, false) === null) {
+ parent::__unset($name);
+ }
+ }
+
+ /**
+ * Declares a `has-one` relation.
+ * The declaration is returned in terms of a relational [[ActiveQuery]] instance
+ * through which the related record can be queried and retrieved back.
+ *
+ * A `has-one` relation means that there is at most one related record matching
+ * the criteria set by this relation, e.g., a customer has one country.
+ *
+ * For example, to declare the `country` relation for `Customer` class, we can write
+ * the following code in the `Customer` class:
+ *
+ * ~~~
+ * public function getCountry()
+ * {
+ * return $this->hasOne(Country::className(), ['id' => 'country_id']);
+ * }
+ * ~~~
+ *
+ * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
+ * in the related class `Country`, while the 'country_id' value refers to an attribute name
+ * in the current AR class.
+ *
+ * Call methods declared in [[ActiveQuery]] to further customize the relation.
+ *
+ * @param string $class the class name of the related record
+ * @param array $link the primary-foreign key constraint. The keys of the array refer to
+ * the attributes of the record associated with the `$class` model, while the values of the
+ * array refer to the corresponding attributes in **this** AR class.
+ * @return ActiveQueryInterface the relational query object.
+ */
+ public function hasOne($class, $link)
+ {
+ /** @var ActiveRecordInterface $class */
+
+ return $class::createQuery([
+ 'modelClass' => $class,
+ 'primaryModel' => $this,
+ 'link' => $link,
+ 'multiple' => false,
+ ]);
+ }
+
+ /**
+ * Declares a `has-many` relation.
+ * The declaration is returned in terms of a relational [[ActiveQuery]] instance
+ * through which the related record can be queried and retrieved back.
+ *
+ * A `has-many` relation means that there are multiple related records matching
+ * the criteria set by this relation, e.g., a customer has many orders.
+ *
+ * For example, to declare the `orders` relation for `Customer` class, we can write
+ * the following code in the `Customer` class:
+ *
+ * ~~~
+ * public function getOrders()
+ * {
+ * return $this->hasMany(Order::className(), ['customer_id' => 'id']);
+ * }
+ * ~~~
+ *
+ * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
+ * an attribute name in the related class `Order`, while the 'id' value refers to
+ * an attribute name in the current AR class.
+ *
+ * Call methods declared in [[ActiveQuery]] to further customize the relation.
+ *
+ * @param string $class the class name of the related record
+ * @param array $link the primary-foreign key constraint. The keys of the array refer to
+ * the attributes of the record associated with the `$class` model, while the values of the
+ * array refer to the corresponding attributes in **this** AR class.
+ * @return ActiveQueryInterface the relational query object.
+ */
+ public function hasMany($class, $link)
+ {
+ /** @var ActiveRecordInterface $class */
+
+ return $class::createQuery([
+ 'modelClass' => $class,
+ 'primaryModel' => $this,
+ 'link' => $link,
+ 'multiple' => true,
+ ]);
+ }
+
+ /**
+ * Populates the named relation with the related records.
+ * Note that this method does not check if the relation exists or not.
+ * @param string $name the relation name (case-sensitive)
+ * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation.
+ */
+ public function populateRelation($name, $records)
+ {
+ $this->_related[$name] = $records;
+ }
+
+ /**
+ * Check whether the named relation has been populated with records.
+ * @param string $name the relation name (case-sensitive)
+ * @return boolean whether relation has been populated with records.
+ */
+ public function isRelationPopulated($name)
+ {
+ return array_key_exists($name, $this->_related);
+ }
+
+ /**
+ * Returns all populated related records.
+ * @return array an array of related records indexed by relation names.
+ */
+ public function getRelatedRecords()
+ {
+ return $this->_related;
+ }
+
+ /**
+ * Returns a value indicating whether the model has an attribute with the specified name.
+ * @param string $name the name of the attribute
+ * @return boolean whether the model has an attribute with the specified name.
+ */
+ public function hasAttribute($name)
+ {
+ return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
+ }
+
+ /**
+ * Returns the named attribute value.
+ * If this record is the result of a query and the attribute is not loaded,
+ * null will be returned.
+ * @param string $name the attribute name
+ * @return mixed the attribute value. Null if the attribute is not set or does not exist.
+ * @see hasAttribute()
+ */
+ public function getAttribute($name)
+ {
+ return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
+ }
+
+ /**
+ * Sets the named attribute value.
+ * @param string $name the attribute name
+ * @param mixed $value the attribute value.
+ * @throws InvalidParamException if the named attribute does not exist.
+ * @see hasAttribute()
+ */
+ public function setAttribute($name, $value)
+ {
+ if ($this->hasAttribute($name)) {
+ $this->_attributes[$name] = $value;
+ } else {
+ throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
+ }
+ }
+
+ /**
+ * Returns the old attribute values.
+ * @return array the old attribute values (name-value pairs)
+ */
+ public function getOldAttributes()
+ {
+ return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
+ }
+
+ /**
+ * Sets the old attribute values.
+ * All existing old attribute values will be discarded.
+ * @param array $values old attribute values to be set.
+ */
+ public function setOldAttributes($values)
+ {
+ $this->_oldAttributes = $values;
+ }
+
+ /**
+ * Returns the old value of the named attribute.
+ * If this record is the result of a query and the attribute is not loaded,
+ * null will be returned.
+ * @param string $name the attribute name
+ * @return mixed the old attribute value. Null if the attribute is not loaded before
+ * or does not exist.
+ * @see hasAttribute()
+ */
+ public function getOldAttribute($name)
+ {
+ return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
+ }
+
+ /**
+ * Sets the old value of the named attribute.
+ * @param string $name the attribute name
+ * @param mixed $value the old attribute value.
+ * @throws InvalidParamException if the named attribute does not exist.
+ * @see hasAttribute()
+ */
+ public function setOldAttribute($name, $value)
+ {
+ if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
+ $this->_oldAttributes[$name] = $value;
+ } else {
+ throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
+ }
+ }
+
+ /**
+ * Marks an attribute dirty.
+ * This method may be called to force updating a record when calling [[update()]],
+ * even if there is no change being made to the record.
+ * @param string $name the attribute name
+ */
+ public function markAttributeDirty($name)
+ {
+ unset($this->_oldAttributes[$name]);
+ }
+
+ /**
+ * Returns a value indicating whether the named attribute has been changed.
+ * @param string $name the name of the attribute
+ * @return boolean whether the attribute has been changed
+ */
+ public function isAttributeChanged($name)
+ {
+ if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
+ return $this->_attributes[$name] !== $this->_oldAttributes[$name];
+ } else {
+ return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
+ }
+ }
+
+ /**
+ * Returns the attribute values that have been modified since they are loaded or saved most recently.
+ * @param string[]|null $names the names of the attributes whose values may be returned if they are
+ * changed recently. If null, [[attributes()]] will be used.
+ * @return array the changed attribute values (name-value pairs)
+ */
+ public function getDirtyAttributes($names = null)
+ {
+ if ($names === null) {
+ $names = $this->attributes();
+ }
+ $names = array_flip($names);
+ $attributes = [];
+ if ($this->_oldAttributes === null) {
+ foreach ($this->_attributes as $name => $value) {
+ if (isset($names[$name])) {
+ $attributes[$name] = $value;
+ }
+ }
+ } else {
+ foreach ($this->_attributes as $name => $value) {
+ if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
+ $attributes[$name] = $value;
+ }
+ }
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Saves the current record.
+ *
+ * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
+ * when [[isNewRecord]] is false.
+ *
+ * For example, to save a customer record:
+ *
+ * ~~~
+ * $customer = new Customer; // or $customer = Customer::find($id);
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->save();
+ * ~~~
+ *
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be saved to database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return boolean whether the saving succeeds
+ */
+ public function save($runValidation = true, $attributes = null)
+ {
+ if ($this->getIsNewRecord()) {
+ return $this->insert($runValidation, $attributes);
+ } else {
+ return $this->update($runValidation, $attributes) !== false;
+ }
+ }
+
+ /**
+ * Saves the changes to this active record into the associated database table.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. save the record into database. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
+ *
+ * For example, to update a customer record:
+ *
+ * ~~~
+ * $customer = Customer::find($id);
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->update();
+ * ~~~
+ *
+ * Note that it is possible the update does not affect any row in the table.
+ * In this case, this method will return 0. For this reason, you should use the following
+ * code to check if update() is successful or not:
+ *
+ * ~~~
+ * if ($this->update() !== false) {
+ * // update successful
+ * } else {
+ * // update failed
+ * }
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return integer|boolean the number of rows affected, or false if validation fails
+ * or [[beforeSave()]] stops the updating process.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being updated is outdated.
+ * @throws \Exception in case update failed.
+ */
+ public function update($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+
+ return $this->updateInternal($attributes);
+ }
+
+ /**
+ * Updates the specified attributes.
+ *
+ * This method is a shortcut to [[update()]] when data validation is not needed
+ * and only a list of attributes need to be updated.
+ *
+ * You may specify the attributes to be updated as name list or name-value pairs.
+ * If the latter, the corresponding attribute values will be modified accordingly.
+ * The method will then save the specified attributes into database.
+ *
+ * Note that this method will NOT perform data validation.
+ *
+ * @param array $attributes the attributes (names or name-value pairs) to be updated
+ * @return integer|boolean the number of rows affected, or false if [[beforeSave()]] stops the updating process.
+ */
+ public function updateAttributes($attributes)
+ {
+ $attrs = [];
+ foreach ($attributes as $name => $value) {
+ if (is_integer($name)) {
+ $attrs[] = $value;
+ } else {
+ $this->$name = $value;
+ $attrs[] = $name;
+ }
+ }
+
+ return $this->update(false, $attrs);
+ }
+
+ /**
+ * @see update()
+ * @throws StaleObjectException
+ */
+ protected function updateInternal($attributes = null)
+ {
+ if (!$this->beforeSave(false)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ $this->afterSave(false);
+
+ return 0;
+ }
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ if (!isset($values[$lock])) {
+ $values[$lock] = $this->$lock + 1;
+ }
+ $condition[$lock] = $this->$lock;
+ }
+ // We do not check the return value of updateAll() because it's possible
+ // that the UPDATE statement doesn't change anything and thus returns 0.
+ $rows = $this->updateAll($values, $condition);
+
+ if ($lock !== null && !$rows) {
+ throw new StaleObjectException('The object being updated is outdated.');
+ }
+
+ foreach ($values as $name => $value) {
+ $this->_oldAttributes[$name] = $this->_attributes[$name];
+ }
+ $this->afterSave(false);
+
+ return $rows;
+ }
+
+ /**
+ * Updates one or several counter columns for the current AR object.
+ * Note that this method differs from [[updateAllCounters()]] in that it only
+ * saves counters for the current AR object.
+ *
+ * An example usage is as follows:
+ *
+ * ~~~
+ * $post = Post::find($id);
+ * $post->updateCounters(['view_count' => 1]);
+ * ~~~
+ *
+ * @param array $counters the counters to be updated (attribute name => increment value)
+ * Use negative values if you want to decrement the counters.
+ * @return boolean whether the saving is successful
+ * @see updateAllCounters()
+ */
+ public function updateCounters($counters)
+ {
+ if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
+ foreach ($counters as $name => $value) {
+ $this->_attributes[$name] += $value;
+ $this->_oldAttributes[$name] = $this->_attributes[$name];
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Deletes the table row corresponding to this active record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 2. delete the record from the database;
+ * 3. call [[afterDelete()]].
+ *
+ * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
+ * will be raised by the corresponding methods.
+ *
+ * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+ * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being deleted is outdated.
+ * @throws \Exception in case delete failed.
+ */
+ public function delete()
+ {
+ $result = false;
+ if ($this->beforeDelete()) {
+ // we do not check the return value of deleteAll() because it's possible
+ // the record is already deleted in the database and thus the method will return 0
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ $condition[$lock] = $this->$lock;
+ }
+ $result = $this->deleteAll($condition);
+ if ($lock !== null && !$result) {
+ throw new StaleObjectException('The object being deleted is outdated.');
+ }
+ $this->_oldAttributes = null;
+ $this->afterDelete();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a value indicating whether the current record is new.
+ * @return boolean whether the record is new and should be inserted when calling [[save()]].
+ */
+ public function getIsNewRecord()
+ {
+ return $this->_oldAttributes === null;
+ }
+
+ /**
+ * Sets the value indicating whether the record is new.
+ * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
+ * @see getIsNewRecord()
+ */
+ public function setIsNewRecord($value)
+ {
+ $this->_oldAttributes = $value ? null : $this->_attributes;
+ }
+
+ /**
+ * Initializes the object.
+ * This method is called at the end of the constructor.
+ * The default implementation will trigger an [[EVENT_INIT]] event.
+ * If you override this method, make sure you call the parent implementation at the end
+ * to ensure triggering of the event.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->trigger(self::EVENT_INIT);
+ }
+
+ /**
+ * This method is called when the AR object is created and populated with the query result.
+ * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
+ * When overriding this method, make sure you call the parent implementation to ensure the
+ * event is triggered.
+ */
+ public function afterFind()
+ {
+ $this->trigger(self::EVENT_AFTER_FIND);
+ }
+
+ /**
+ * This method is called at the beginning of inserting or updating a record.
+ * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
+ * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
+ * When overriding this method, make sure you call the parent implementation like the following:
+ *
+ * ~~~
+ * public function beforeSave($insert)
+ * {
+ * if (parent::beforeSave($insert)) {
+ * // ...custom code here...
+ * return true;
+ * } else {
+ * return false;
+ * }
+ * }
+ * ~~~
+ *
+ * @param boolean $insert whether this method called while inserting a record.
+ * If false, it means the method is called while updating a record.
+ * @return boolean whether the insertion or updating should continue.
+ * If false, the insertion or updating will be cancelled.
+ */
+ public function beforeSave($insert)
+ {
+ $event = new ModelEvent;
+ $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
+
+ return $event->isValid;
+ }
+
+ /**
+ * This method is called at the end of inserting or updating a record.
+ * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
+ * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false.
+ * When overriding this method, make sure you call the parent implementation so that
+ * the event is triggered.
+ * @param boolean $insert whether this method called while inserting a record.
+ * If false, it means the method is called while updating a record.
+ */
+ public function afterSave($insert)
+ {
+ $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE);
+ }
+
+ /**
+ * This method is invoked before deleting a record.
+ * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
+ * When overriding this method, make sure you call the parent implementation like the following:
+ *
+ * ~~~
+ * public function beforeDelete()
+ * {
+ * if (parent::beforeDelete()) {
+ * // ...custom code here...
+ * return true;
+ * } else {
+ * return false;
+ * }
+ * }
+ * ~~~
+ *
+ * @return boolean whether the record should be deleted. Defaults to true.
+ */
+ public function beforeDelete()
+ {
+ $event = new ModelEvent;
+ $this->trigger(self::EVENT_BEFORE_DELETE, $event);
+
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked after deleting a record.
+ * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
+ * You may override this method to do postprocessing after the record is deleted.
+ * Make sure you call the parent implementation so that the event is raised properly.
+ */
+ public function afterDelete()
+ {
+ $this->trigger(self::EVENT_AFTER_DELETE);
+ }
+
+ /**
+ * Repopulates this active record with the latest data.
+ * @return boolean whether the row still exists in the database. If true, the latest data
+ * will be populated to this active record. Otherwise, this record will remain unchanged.
+ */
+ public function refresh()
+ {
+ $record = $this->find($this->getPrimaryKey(true));
+ if ($record === null) {
+ return false;
+ }
+ foreach ($this->attributes() as $name) {
+ $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
+ }
+ $this->_oldAttributes = $this->_attributes;
+ $this->_related = [];
+
+ return true;
+ }
+
+ /**
+ * Returns a value indicating whether the given active record is the same as the current one.
+ * The comparison is made by comparing the table names and the primary key values of the two active records.
+ * If one of the records [[isNewRecord|is new]] they are also considered not equal.
+ * @param ActiveRecordInterface $record record to compare to
+ * @return boolean whether the two active records refer to the same row in the same database table.
+ */
+ public function equals($record)
+ {
+ if ($this->getIsNewRecord() || $record->getIsNewRecord()) {
+ return false;
+ }
+
+ return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey();
+ }
+
+ /**
+ * Returns the primary key value(s).
+ * @param boolean $asArray whether to return the primary key value as an array. If true,
+ * the return value will be an array with column names as keys and column values as values.
+ * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
+ * @property mixed The primary key value. An array (column name => column value) is returned if
+ * the primary key is composite. A string is returned otherwise (null will be returned if
+ * the key value is null).
+ * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
+ * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+ * the key value is null).
+ */
+ public function getPrimaryKey($asArray = false)
+ {
+ $keys = $this->primaryKey();
+ if (count($keys) === 1 && !$asArray) {
+ return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
+ } else {
+ $values = [];
+ foreach ($keys as $name) {
+ $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
+ }
+
+ return $values;
+ }
+ }
+
+ /**
+ * Returns the old primary key value(s).
+ * This refers to the primary key value that is populated into the record
+ * after executing a find method (e.g. find(), findAll()).
+ * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
+ * @param boolean $asArray whether to return the primary key value as an array. If true,
+ * the return value will be an array with column name as key and column value as value.
+ * If this is false (default), a scalar value will be returned for non-composite primary key.
+ * @property mixed The old primary key value. An array (column name => column value) is
+ * returned if the primary key is composite. A string is returned otherwise (null will be
+ * returned if the key value is null).
+ * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
+ * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+ * the key value is null).
+ */
+ public function getOldPrimaryKey($asArray = false)
+ {
+ $keys = $this->primaryKey();
+ if (count($keys) === 1 && !$asArray) {
+ return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
+ } else {
+ $values = [];
+ foreach ($keys as $name) {
+ $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
+ }
+
+ return $values;
+ }
+ }
+
+ /**
+ * Populates an active record object using a row of data from the database/storage.
+ *
+ * This is an internal method meant to be called to create active record objects after
+ * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
+ * the query results into active records.
+ *
+ * When calling this method manually you should call [[afterFind()]] on the created
+ * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
+ *
+ * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
+ * created by [[instantiate()]] beforehand.
+ * @param array $row attribute values (name => value)
+ */
+ public static function populateRecord($record, $row)
+ {
+ $columns = array_flip($record->attributes());
+ foreach ($row as $name => $value) {
+ if (isset($columns[$name])) {
+ $record->_attributes[$name] = $value;
+ } else {
+ $record->$name = $value;
+ }
+ }
+ $record->_oldAttributes = $record->_attributes;
+ }
+
+ /**
+ * Creates an active record instance.
+ *
+ * This method is called together with [[populateRecord()]] by [[ActiveQuery]].
+ * It is not meant to be used for creating new records directly.
+ *
+ * You may override this method if the instance being created
+ * depends on the row data to be populated into the record.
+ * For example, by creating a record based on the value of a column,
+ * you may implement the so-called single-table inheritance mapping.
+ * @param array $row row data to be populated into the record.
+ * @return static the newly created active record
+ */
+ public static function instantiate($row)
+ {
+ return new static;
+ }
+
+ /**
+ * Returns whether there is an element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to check on
+ * @return boolean whether there is an element at the specified offset.
+ */
+ public function offsetExists($offset)
+ {
+ return $this->__isset($offset);
+ }
+
+ /**
+ * Returns the relation object with the specified name.
+ * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object.
+ * It can be declared in either the Active Record class itself or one of its behaviors.
+ * @param string $name the relation name
+ * @param boolean $throwException whether to throw exception if the relation does not exist.
+ * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist
+ * and `$throwException` is false, null will be returned.
+ * @throws InvalidParamException if the named relation does not exist.
+ */
+ public function getRelation($name, $throwException = true)
+ {
+ $getter = 'get' . $name;
+ try {
+ // the relation could be defined in a behavior
+ $relation = $this->$getter();
+ } catch (UnknownMethodException $e) {
+ if ($throwException) {
+ throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
+ } else {
+ return null;
+ }
+ }
+ if (!$relation instanceof ActiveQueryInterface) {
+ if ($throwException) {
+ throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
+ } else {
+ return null;
+ }
+ }
+
+ if (method_exists($this, $getter)) {
+ // relation name is case sensitive, trying to validate it when the relation is defined within this class
+ $method = new \ReflectionMethod($this, $getter);
+ $realName = lcfirst(substr($method->getName(), 3));
+ if ($realName !== $name) {
+ if ($throwException) {
+ throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\".");
+ } else {
+ return null;
+ }
+ }
+ }
+
+ return $relation;
+ }
+
+ /**
+ * Establishes the relationship between two models.
+ *
+ * The relationship is established by setting the foreign key value(s) in one model
+ * to be the corresponding primary key value(s) in the other model.
+ * The model with the foreign key will be saved into database without performing validation.
+ *
+ * If the relationship involves a pivot table, a new row will be inserted into the
+ * pivot table which contains the primary key values from both models.
+ *
+ * Note that this method requires that the primary key value is not null.
+ *
+ * @param string $name the case sensitive name of the relationship
+ * @param ActiveRecordInterface $model the model to be linked with the current one.
+ * @param array $extraColumns additional column values to be saved into the pivot table.
+ * This parameter is only meaningful for a relationship involving a pivot table
+ * (i.e., a relation set with [[ActiveRelationTrait::via()]] or `[[ActiveQuery::viaTable()]]`.)
+ * @throws InvalidCallException if the method is unable to link two models.
+ */
+ public function link($name, $model, $extraColumns = [])
+ {
+ $relation = $this->getRelation($name);
+
+ if ($relation->via !== null) {
+ if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
+ throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
+ }
+ if (is_array($relation->via)) {
+ /** @var ActiveQuery $viaRelation */
+ list($viaName, $viaRelation) = $relation->via;
+ $viaClass = $viaRelation->modelClass;
+ // unset $viaName so that it can be reloaded to reflect the change
+ unset($this->_related[$viaName]);
+ } else {
+ $viaRelation = $relation->via;
+ $viaTable = reset($relation->via->from);
+ }
+ $columns = [];
+ foreach ($viaRelation->link as $a => $b) {
+ $columns[$a] = $this->$b;
+ }
+ foreach ($relation->link as $a => $b) {
+ $columns[$b] = $model->$a;
+ }
+ foreach ($extraColumns as $k => $v) {
+ $columns[$k] = $v;
+ }
+ if (is_array($relation->via)) {
+ /** @var $viaClass ActiveRecordInterface */
+ /** @var $record ActiveRecordInterface */
+ $record = new $viaClass();
+ foreach ($columns as $column => $value) {
+ $record->$column = $value;
+ }
+ $record->insert(false);
+ } else {
+ /** @var $viaTable string */
+ static::getDb()->createCommand()
+ ->insert($viaTable, $columns)->execute();
+ }
+ } else {
+ $p1 = $model->isPrimaryKey(array_keys($relation->link));
+ $p2 = $this->isPrimaryKey(array_values($relation->link));
+ if ($p1 && $p2) {
+ if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
+ throw new InvalidCallException('Unable to link models: both models are newly created.');
+ } elseif ($this->getIsNewRecord()) {
+ $this->bindModels(array_flip($relation->link), $this, $model);
+ } else {
+ $this->bindModels($relation->link, $model, $this);
+ }
+ } elseif ($p1) {
+ $this->bindModels(array_flip($relation->link), $this, $model);
+ } elseif ($p2) {
+ $this->bindModels($relation->link, $model, $this);
+ } else {
+ throw new InvalidCallException('Unable to link models: the link does not involve any primary key.');
+ }
+ }
+
+ // update lazily loaded related objects
+ if (!$relation->multiple) {
+ $this->_related[$name] = $model;
+ } elseif (isset($this->_related[$name])) {
+ if ($relation->indexBy !== null) {
+ $indexBy = $relation->indexBy;
+ $this->_related[$name][$model->$indexBy] = $model;
+ } else {
+ $this->_related[$name][] = $model;
+ }
+ }
+ }
+
+ /**
+ * Destroys the relationship between two models.
+ *
+ * The model with the foreign key of the relationship will be deleted if `$delete` is true.
+ * Otherwise, the foreign key will be set null and the model will be saved without validation.
+ *
+ * @param string $name the case sensitive name of the relationship.
+ * @param ActiveRecordInterface $model the model to be unlinked from the current one.
+ * @param boolean $delete whether to delete the model that contains the foreign key.
+ * If false, the model's foreign key will be set null and saved.
+ * If true, the model containing the foreign key will be deleted.
+ * @throws InvalidCallException if the models cannot be unlinked
+ */
+ public function unlink($name, $model, $delete = false)
+ {
+ $relation = $this->getRelation($name);
+
+ if ($relation->via !== null) {
+ if (is_array($relation->via)) {
+ /** @var ActiveQuery $viaRelation */
+ list($viaName, $viaRelation) = $relation->via;
+ $viaClass = $viaRelation->modelClass;
+ unset($this->_related[$viaName]);
+ } else {
+ $viaRelation = $relation->via;
+ $viaTable = reset($relation->via->from);
+ }
+ $columns = [];
+ foreach ($viaRelation->link as $a => $b) {
+ $columns[$a] = $this->$b;
+ }
+ foreach ($relation->link as $a => $b) {
+ $columns[$b] = $model->$a;
+ }
+ if (is_array($relation->via)) {
+ /** @var $viaClass ActiveRecordInterface */
+ if ($delete) {
+ $viaClass::deleteAll($columns);
+ } else {
+ $nulls = [];
+ foreach (array_keys($columns) as $a) {
+ $nulls[$a] = null;
+ }
+ $viaClass::updateAll($nulls, $columns);
+ }
+ } else {
+ /** @var $viaTable string */
+ /** @var Command $command */
+ $command = static::getDb()->createCommand();
+ if ($delete) {
+ $command->delete($viaTable, $columns)->execute();
+ } else {
+ $nulls = [];
+ foreach (array_keys($columns) as $a) {
+ $nulls[$a] = null;
+ }
+ $command->update($viaTable, $nulls, $columns)->execute();
+ }
+ }
+ } else {
+ $p1 = $model->isPrimaryKey(array_keys($relation->link));
+ $p2 = $this->isPrimaryKey(array_values($relation->link));
+ if ($p1 && $p2 || $p2) {
+ foreach ($relation->link as $a => $b) {
+ $model->$a = null;
+ }
+ $delete ? $model->delete() : $model->save(false);
+ } elseif ($p1) {
+ foreach ($relation->link as $b) {
+ $this->$b = null;
+ }
+ $delete ? $this->delete() : $this->save(false);
+ } else {
+ throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
+ }
+ }
+
+ if (!$relation->multiple) {
+ unset($this->_related[$name]);
+ } elseif (isset($this->_related[$name])) {
+ /** @var ActiveRecordInterface $b */
+ foreach ($this->_related[$name] as $a => $b) {
+ if ($model->getPrimaryKey() == $b->getPrimaryKey()) {
+ unset($this->_related[$name][$a]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $link
+ * @param BaseActiveRecord $foreignModel
+ * @param BaseActiveRecord $primaryModel
+ * @throws InvalidCallException
+ */
+ private function bindModels($link, $foreignModel, $primaryModel)
+ {
+ foreach ($link as $fk => $pk) {
+ $value = $primaryModel->$pk;
+ if ($value === null) {
+ throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
+ }
+ $foreignModel->$fk = $value;
+ }
+ $foreignModel->save(false);
+ }
+
+ /**
+ * Returns a value indicating whether the given set of attributes represents the primary key for this model
+ * @param array $keys the set of attributes to check
+ * @return boolean whether the given set of attributes represents the primary key for this model
+ */
+ public static function isPrimaryKey($keys)
+ {
+ $pks = static::primaryKey();
+ if (count($keys) === count($pks)) {
+ return count(array_intersect($keys, $pks)) === count($pks);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the text label for the specified attribute.
+ * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
+ * @param string $attribute the attribute name
+ * @return string the attribute label
+ * @see generateAttributeLabel()
+ * @see attributeLabels()
+ */
+ public function getAttributeLabel($attribute)
+ {
+ $labels = $this->attributeLabels();
+ if (isset($labels[$attribute])) {
+ return ($labels[$attribute]);
+ } elseif (strpos($attribute, '.')) {
+ $attributeParts = explode('.', $attribute);
+ $neededAttribute = array_pop($attributeParts);
+
+ $relatedModel = $this;
+ foreach ($attributeParts as $relationName) {
+ if (isset($this->_related[$relationName]) && $this->_related[$relationName] instanceof self) {
+ $relatedModel = $this->_related[$relationName];
+ } else {
+ try {
+ $relation = $relatedModel->getRelation($relationName);
+ } catch (InvalidParamException $e) {
+ return $this->generateAttributeLabel($attribute);
+ }
+ $relatedModel = new $relation->modelClass;
+ }
+ }
+
+ $labels = $relatedModel->attributeLabels();
+ if (isset($labels[$neededAttribute])) {
+ return $labels[$neededAttribute];
+ }
+ }
+
+ return $this->generateAttributeLabel($attribute);
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * The default implementation returns the names of the columns whose values have been populated into this record.
+ */
+ public function fields()
+ {
+ $fields = array_keys($this->_attributes);
+
+ return array_combine($fields, $fields);
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * The default implementation returns the names of the relations that have been populated into this record.
+ */
+ public function extraFields()
+ {
+ $fields = array_keys($this->getRelatedRecords());
+
+ return array_combine($fields, $fields);
+ }
+
+ /**
+ * Sets the element value at the specified offset to null.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `unset($model[$offset])`.
+ * @param mixed $offset the offset to unset element
+ */
+ public function offsetUnset($offset)
+ {
+ if (property_exists($this, $offset)) {
+ $this->$offset = null;
+ } else {
+ unset($this->$offset);
+ }
+ }
}
diff --git a/framework/db/BatchQueryResult.php b/framework/db/BatchQueryResult.php
index 3a1ba117ef8..3c98d65eefc 100644
--- a/framework/db/BatchQueryResult.php
+++ b/framework/db/BatchQueryResult.php
@@ -30,148 +30,148 @@
*/
class BatchQueryResult extends Object implements \Iterator
{
- /**
- * @var Connection the DB connection to be used when performing batch query.
- * If null, the "db" application component will be used.
- */
- public $db;
- /**
- * @var Query the query object associated with this batch query.
- * Do not modify this property directly unless after [[reset()]] is called explicitly.
- */
- public $query;
- /**
- * @var integer the number of rows to be returned in each batch.
- */
- public $batchSize = 100;
- /**
- * @var boolean whether to return a single row during each iteration.
- * If false, a whole batch of rows will be returned in each iteration.
- */
- public $each = false;
- /**
- * @var DataReader the data reader associated with this batch query.
- */
- private $_dataReader;
- /**
- * @var array the data retrieved in the current batch
- */
- private $_batch;
- /**
- * @var mixed the value for the current iteration
- */
- private $_value;
- /**
- * @var string|integer the key for the current iteration
- */
- private $_key;
+ /**
+ * @var Connection the DB connection to be used when performing batch query.
+ * If null, the "db" application component will be used.
+ */
+ public $db;
+ /**
+ * @var Query the query object associated with this batch query.
+ * Do not modify this property directly unless after [[reset()]] is called explicitly.
+ */
+ public $query;
+ /**
+ * @var integer the number of rows to be returned in each batch.
+ */
+ public $batchSize = 100;
+ /**
+ * @var boolean whether to return a single row during each iteration.
+ * If false, a whole batch of rows will be returned in each iteration.
+ */
+ public $each = false;
+ /**
+ * @var DataReader the data reader associated with this batch query.
+ */
+ private $_dataReader;
+ /**
+ * @var array the data retrieved in the current batch
+ */
+ private $_batch;
+ /**
+ * @var mixed the value for the current iteration
+ */
+ private $_value;
+ /**
+ * @var string|integer the key for the current iteration
+ */
+ private $_key;
- /**
- * Destructor.
- */
- public function __destruct()
- {
- // make sure cursor is closed
- $this->reset();
- }
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ // make sure cursor is closed
+ $this->reset();
+ }
- /**
- * Resets the batch query.
- * This method will clean up the existing batch query so that a new batch query can be performed.
- */
- public function reset()
- {
- if ($this->_dataReader !== null) {
- $this->_dataReader->close();
- }
- $this->_dataReader = null;
- $this->_batch = null;
- $this->_value = null;
- $this->_key = null;
- }
+ /**
+ * Resets the batch query.
+ * This method will clean up the existing batch query so that a new batch query can be performed.
+ */
+ public function reset()
+ {
+ if ($this->_dataReader !== null) {
+ $this->_dataReader->close();
+ }
+ $this->_dataReader = null;
+ $this->_batch = null;
+ $this->_value = null;
+ $this->_key = null;
+ }
- /**
- * Resets the iterator to the initial state.
- * This method is required by the interface Iterator.
- */
- public function rewind()
- {
- $this->reset();
- $this->next();
- }
+ /**
+ * Resets the iterator to the initial state.
+ * This method is required by the interface Iterator.
+ */
+ public function rewind()
+ {
+ $this->reset();
+ $this->next();
+ }
- /**
- * Moves the internal pointer to the next dataset.
- * This method is required by the interface Iterator.
- */
- public function next()
- {
- if ($this->_batch === null || !$this->each || $this->each && next($this->_batch) === false) {
- $this->_batch = $this->fetchData();
- reset($this->_batch);
- }
+ /**
+ * Moves the internal pointer to the next dataset.
+ * This method is required by the interface Iterator.
+ */
+ public function next()
+ {
+ if ($this->_batch === null || !$this->each || $this->each && next($this->_batch) === false) {
+ $this->_batch = $this->fetchData();
+ reset($this->_batch);
+ }
- if ($this->each) {
- $this->_value = current($this->_batch);
- if ($this->query->indexBy !== null) {
- $this->_key = key($this->_batch);
- } elseif (key($this->_batch) !== null) {
- $this->_key++;
- } else {
- $this->_key = null;
- }
- } else {
- $this->_value = $this->_batch;
- $this->_key = $this->_key === null ? 0 : $this->_key + 1;
- }
- }
+ if ($this->each) {
+ $this->_value = current($this->_batch);
+ if ($this->query->indexBy !== null) {
+ $this->_key = key($this->_batch);
+ } elseif (key($this->_batch) !== null) {
+ $this->_key++;
+ } else {
+ $this->_key = null;
+ }
+ } else {
+ $this->_value = $this->_batch;
+ $this->_key = $this->_key === null ? 0 : $this->_key + 1;
+ }
+ }
- /**
- * Fetches the next batch of data.
- * @return array the data fetched
- */
- protected function fetchData()
- {
- if ($this->_dataReader === null) {
- $this->_dataReader = $this->query->createCommand($this->db)->query();
- }
+ /**
+ * Fetches the next batch of data.
+ * @return array the data fetched
+ */
+ protected function fetchData()
+ {
+ if ($this->_dataReader === null) {
+ $this->_dataReader = $this->query->createCommand($this->db)->query();
+ }
- $rows = [];
- $count = 0;
- while ($count++ < $this->batchSize && ($row = $this->_dataReader->read())) {
- $rows[] = $row;
- }
+ $rows = [];
+ $count = 0;
+ while ($count++ < $this->batchSize && ($row = $this->_dataReader->read())) {
+ $rows[] = $row;
+ }
- return $this->query->prepareResult($rows);
- }
+ return $this->query->prepareResult($rows);
+ }
- /**
- * Returns the index of the current dataset.
- * This method is required by the interface Iterator.
- * @return integer the index of the current row.
- */
- public function key()
- {
- return $this->_key;
- }
+ /**
+ * Returns the index of the current dataset.
+ * This method is required by the interface Iterator.
+ * @return integer the index of the current row.
+ */
+ public function key()
+ {
+ return $this->_key;
+ }
- /**
- * Returns the current dataset.
- * This method is required by the interface Iterator.
- * @return mixed the current dataset.
- */
- public function current()
- {
- return $this->_value;
- }
+ /**
+ * Returns the current dataset.
+ * This method is required by the interface Iterator.
+ * @return mixed the current dataset.
+ */
+ public function current()
+ {
+ return $this->_value;
+ }
- /**
- * Returns whether there is a valid dataset at the current position.
- * This method is required by the interface Iterator.
- * @return boolean whether there is a valid dataset at the current position.
- */
- public function valid()
- {
- return !empty($this->_batch);
- }
+ /**
+ * Returns whether there is a valid dataset at the current position.
+ * This method is required by the interface Iterator.
+ * @return boolean whether there is a valid dataset at the current position.
+ */
+ public function valid()
+ {
+ return !empty($this->_batch);
+ }
}
diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php
index 3e7f6cf43f6..83dce13c109 100644
--- a/framework/db/ColumnSchema.php
+++ b/framework/db/ColumnSchema.php
@@ -17,90 +17,90 @@
*/
class ColumnSchema extends Object
{
- /**
- * @var string name of this column (without quotes).
- */
- public $name;
- /**
- * @var boolean whether this column can be null.
- */
- public $allowNull;
- /**
- * @var string abstract type of this column. Possible abstract types include:
- * string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
- * timestamp, time, date, binary, and money.
- */
- public $type;
- /**
- * @var string the PHP type of this column. Possible PHP types include:
- * string, boolean, integer, double.
- */
- public $phpType;
- /**
- * @var string the DB type of this column. Possible DB types vary according to the type of DBMS.
- */
- public $dbType;
- /**
- * @var mixed default value of this column
- */
- public $defaultValue;
- /**
- * @var array enumerable values. This is set only if the column is declared to be an enumerable type.
- */
- public $enumValues;
- /**
- * @var integer display size of the column.
- */
- public $size;
- /**
- * @var integer precision of the column data, if it is numeric.
- */
- public $precision;
- /**
- * @var integer scale of the column data, if it is numeric.
- */
- public $scale;
- /**
- * @var boolean whether this column is a primary key
- */
- public $isPrimaryKey;
- /**
- * @var boolean whether this column is auto-incremental
- */
- public $autoIncrement = false;
- /**
- * @var boolean whether this column is unsigned. This is only meaningful
- * when [[type]] is `smallint`, `integer` or `bigint`.
- */
- public $unsigned;
- /**
- * @var string comment of this column. Not all DBMS support this.
- */
- public $comment;
+ /**
+ * @var string name of this column (without quotes).
+ */
+ public $name;
+ /**
+ * @var boolean whether this column can be null.
+ */
+ public $allowNull;
+ /**
+ * @var string abstract type of this column. Possible abstract types include:
+ * string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
+ * timestamp, time, date, binary, and money.
+ */
+ public $type;
+ /**
+ * @var string the PHP type of this column. Possible PHP types include:
+ * string, boolean, integer, double.
+ */
+ public $phpType;
+ /**
+ * @var string the DB type of this column. Possible DB types vary according to the type of DBMS.
+ */
+ public $dbType;
+ /**
+ * @var mixed default value of this column
+ */
+ public $defaultValue;
+ /**
+ * @var array enumerable values. This is set only if the column is declared to be an enumerable type.
+ */
+ public $enumValues;
+ /**
+ * @var integer display size of the column.
+ */
+ public $size;
+ /**
+ * @var integer precision of the column data, if it is numeric.
+ */
+ public $precision;
+ /**
+ * @var integer scale of the column data, if it is numeric.
+ */
+ public $scale;
+ /**
+ * @var boolean whether this column is a primary key
+ */
+ public $isPrimaryKey;
+ /**
+ * @var boolean whether this column is auto-incremental
+ */
+ public $autoIncrement = false;
+ /**
+ * @var boolean whether this column is unsigned. This is only meaningful
+ * when [[type]] is `smallint`, `integer` or `bigint`.
+ */
+ public $unsigned;
+ /**
+ * @var string comment of this column. Not all DBMS support this.
+ */
+ public $comment;
+ /**
+ * Converts the input value according to [[phpType]].
+ * If the value is null or an [[Expression]], it will not be converted.
+ * @param mixed $value input value
+ * @return mixed converted value
+ */
+ public function typecast($value)
+ {
+ if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) {
+ return null;
+ }
+ if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
+ return $value;
+ }
+ switch ($this->phpType) {
+ case 'string':
+ return (string) $value;
+ case 'integer':
+ return (integer) $value;
+ case 'boolean':
+ return (boolean) $value;
+ }
- /**
- * Converts the input value according to [[phpType]].
- * If the value is null or an [[Expression]], it will not be converted.
- * @param mixed $value input value
- * @return mixed converted value
- */
- public function typecast($value)
- {
- if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) {
- return null;
- }
- if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
- return $value;
- }
- switch ($this->phpType) {
- case 'string':
- return (string)$value;
- case 'integer':
- return (integer)$value;
- case 'boolean':
- return (boolean)$value;
- }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/framework/db/Command.php b/framework/db/Command.php
index 6e3ddb7657b..bf29e72a909 100644
--- a/framework/db/Command.php
+++ b/framework/db/Command.php
@@ -53,704 +53,730 @@
*/
class Command extends \yii\base\Component
{
- /**
- * @var Connection the DB connection that this command is associated with
- */
- public $db;
- /**
- * @var \PDOStatement the PDOStatement object that this command is associated with
- */
- public $pdoStatement;
- /**
- * @var integer the default fetch mode for this command.
- * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
- */
- public $fetchMode = \PDO::FETCH_ASSOC;
- /**
- * @var array the parameters (name => value) that are bound to the current PDO statement.
- * This property is maintained by methods such as [[bindValue()]].
- * Do not modify it directly.
- */
- public $params = [];
- /**
- * @var string the SQL statement that this command represents
- */
- private $_sql;
-
- /**
- * Returns the SQL statement for this command.
- * @return string the SQL statement to be executed
- */
- public function getSql()
- {
- return $this->_sql;
- }
-
- /**
- * Specifies the SQL statement to be executed.
- * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well.
- * @param string $sql the SQL statement to be set.
- * @return static this command instance
- */
- public function setSql($sql)
- {
- if ($sql !== $this->_sql) {
- $this->cancel();
- $this->_sql = $this->db->quoteSql($sql);
- $this->params = [];
- }
- return $this;
- }
-
- /**
- * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
- * Note that the return value of this method should mainly be used for logging purpose.
- * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
- * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]].
- */
- public function getRawSql()
- {
- if (empty($this->params)) {
- return $this->_sql;
- } else {
- $params = [];
- foreach ($this->params as $name => $value) {
- if (is_string($value)) {
- $params[$name] = $this->db->quoteValue($value);
- } elseif ($value === null) {
- $params[$name] = 'NULL';
- } else {
- $params[$name] = $value;
- }
- }
- if (isset($params[1])) {
- $sql = '';
- foreach (explode('?', $this->_sql) as $i => $part) {
- $sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
- }
- return $sql;
- } else {
- return strtr($this->_sql, $params);
- }
- }
- }
-
- /**
- * Prepares the SQL statement to be executed.
- * For complex SQL statement that is to be executed multiple times,
- * this may improve performance.
- * For SQL statement with binding parameters, this method is invoked
- * automatically.
- * @throws Exception if there is any DB error
- */
- public function prepare()
- {
- if ($this->pdoStatement == null) {
- $sql = $this->getSql();
- try {
- $this->pdoStatement = $this->db->pdo->prepare($sql);
- } catch (\Exception $e) {
- $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
- $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
- throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
- }
- }
- }
-
- /**
- * Cancels the execution of the SQL statement.
- * This method mainly sets [[pdoStatement]] to be null.
- */
- public function cancel()
- {
- $this->pdoStatement = null;
- }
-
- /**
- * Binds a parameter to the SQL statement to be executed.
- * @param string|integer $name parameter identifier. For a prepared statement
- * using named placeholders, this will be a parameter name of
- * the form `:name`. For a prepared statement using question mark
- * placeholders, this will be the 1-indexed position of the parameter.
- * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter
- * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
- * @param integer $length length of the data type
- * @param mixed $driverOptions the driver-specific options
- * @return static the current command being executed
- * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php
- */
- public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
- {
- $this->prepare();
- if ($dataType === null) {
- $dataType = $this->db->getSchema()->getPdoType($value);
- }
- if ($length === null) {
- $this->pdoStatement->bindParam($name, $value, $dataType);
- } elseif ($driverOptions === null) {
- $this->pdoStatement->bindParam($name, $value, $dataType, $length);
- } else {
- $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
- }
- $this->params[$name] =& $value;
- return $this;
- }
-
- /**
- * Binds a value to a parameter.
- * @param string|integer $name Parameter identifier. For a prepared statement
- * using named placeholders, this will be a parameter name of
- * the form `:name`. For a prepared statement using question mark
- * placeholders, this will be the 1-indexed position of the parameter.
- * @param mixed $value The value to bind to the parameter
- * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
- * @return static the current command being executed
- * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php
- */
- public function bindValue($name, $value, $dataType = null)
- {
- $this->prepare();
- if ($dataType === null) {
- $dataType = $this->db->getSchema()->getPdoType($value);
- }
- $this->pdoStatement->bindValue($name, $value, $dataType);
- $this->params[$name] = $value;
- return $this;
- }
-
- /**
- * Binds a list of values to the corresponding parameters.
- * This is similar to [[bindValue()]] except that it binds multiple values at a time.
- * Note that the SQL data type of each value is determined by its PHP type.
- * @param array $values the values to be bound. This must be given in terms of an associative
- * array with array keys being the parameter names, and array values the corresponding parameter values,
- * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined
- * by its PHP type. You may explicitly specify the PDO type by using an array: `[value, type]`,
- * e.g. `[':name' => 'John', ':profile' => [$profile, \PDO::PARAM_LOB]]`.
- * @return static the current command being executed
- */
- public function bindValues($values)
- {
- if (!empty($values)) {
- $this->prepare();
- foreach ($values as $name => $value) {
- if (is_array($value)) {
- $type = $value[1];
- $value = $value[0];
- } else {
- $type = $this->db->getSchema()->getPdoType($value);
- }
- $this->pdoStatement->bindValue($name, $value, $type);
- $this->params[$name] = $value;
- }
- }
- return $this;
- }
-
- /**
- * Executes the SQL statement.
- * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
- * No result set will be returned.
- * @return integer number of rows affected by the execution.
- * @throws Exception execution failed
- */
- public function execute()
- {
- $sql = $this->getSql();
-
- $rawSql = $this->getRawSql();
-
- Yii::info($rawSql, __METHOD__);
-
- if ($sql == '') {
- return 0;
- }
-
- $token = $rawSql;
- try {
- Yii::beginProfile($token, __METHOD__);
-
- $this->prepare();
- $this->pdoStatement->execute();
- $n = $this->pdoStatement->rowCount();
-
- Yii::endProfile($token, __METHOD__);
- return $n;
- } catch (\Exception $e) {
- Yii::endProfile($token, __METHOD__);
- if ($e instanceof Exception) {
- throw $e;
- } else {
- $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
- $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
- throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
- }
- }
- }
-
- /**
- * Executes the SQL statement and returns query result.
- * This method is for executing a SQL query that returns result set, such as `SELECT`.
- * @return DataReader the reader object for fetching the query result
- * @throws Exception execution failed
- */
- public function query()
- {
- return $this->queryInternal('');
- }
-
- /**
- * Executes the SQL statement and returns ALL rows at once.
- * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
- * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
- * @return array all rows of the query result. Each array element is an array representing a row of data.
- * An empty array is returned if the query results in nothing.
- * @throws Exception execution failed
- */
- public function queryAll($fetchMode = null)
- {
- return $this->queryInternal('fetchAll', $fetchMode);
- }
-
- /**
- * Executes the SQL statement and returns the first row of the result.
- * This method is best used when only the first row of result is needed for a query.
- * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
- * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
- * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
- * results in nothing.
- * @throws Exception execution failed
- */
- public function queryOne($fetchMode = null)
- {
- return $this->queryInternal('fetch', $fetchMode);
- }
-
- /**
- * Executes the SQL statement and returns the value of the first column in the first row of data.
- * This method is best used when only a single value is needed for a query.
- * @return string|boolean the value of the first column in the first row of the query result.
- * False is returned if there is no value.
- * @throws Exception execution failed
- */
- public function queryScalar()
- {
- $result = $this->queryInternal('fetchColumn', 0);
- if (is_resource($result) && get_resource_type($result) === 'stream') {
- return stream_get_contents($result);
- } else {
- return $result;
- }
- }
-
- /**
- * Executes the SQL statement and returns the first column of the result.
- * This method is best used when only the first column of result (i.e. the first element in each row)
- * is needed for a query.
- * @return array the first column of the query result. Empty array is returned if the query results in nothing.
- * @throws Exception execution failed
- */
- public function queryColumn()
- {
- return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN);
- }
-
- /**
- * Performs the actual DB query of a SQL statement.
- * @param string $method method of PDOStatement to be called
- * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
- * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
- * @return mixed the method execution result
- * @throws Exception if the query causes any problem
- */
- private function queryInternal($method, $fetchMode = null)
- {
- $db = $this->db;
- $rawSql = $this->getRawSql();
-
- Yii::info($rawSql, 'yii\db\Command::query');
-
- /** @var \yii\caching\Cache $cache */
- if ($db->enableQueryCache && $method !== '') {
- $cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache;
- }
-
- if (isset($cache) && $cache instanceof Cache) {
- $cacheKey = [
- __CLASS__,
- $method,
- $db->dsn,
- $db->username,
- $rawSql,
- ];
- if (($result = $cache->get($cacheKey)) !== false) {
- Yii::trace('Query result served from cache', 'yii\db\Command::query');
- return $result;
- }
- }
-
- $token = $rawSql;
- try {
- Yii::beginProfile($token, 'yii\db\Command::query');
-
- $this->prepare();
- $this->pdoStatement->execute();
-
- if ($method === '') {
- $result = new DataReader($this);
- } else {
- if ($fetchMode === null) {
- $fetchMode = $this->fetchMode;
- }
- $result = call_user_func_array([$this->pdoStatement, $method], (array)$fetchMode);
- $this->pdoStatement->closeCursor();
- }
-
- Yii::endProfile($token, 'yii\db\Command::query');
-
- if (isset($cache, $cacheKey) && $cache instanceof Cache) {
- $cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
- Yii::trace('Saved query result in cache', 'yii\db\Command::query');
- }
-
- return $result;
- } catch (\Exception $e) {
- Yii::endProfile($token, 'yii\db\Command::query');
- if ($e instanceof Exception) {
- throw $e;
- } else {
- $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
- $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
- throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
- }
- }
- }
-
- /**
- * Creates an INSERT command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->insert('tbl_user', [
- * 'name' => 'Sam',
- * 'age' => 30,
- * ])->execute();
- * ~~~
- *
- * The method will properly escape the column names, and bind the values to be inserted.
- *
- * Note that the created command is not executed until [[execute()]] is called.
- *
- * @param string $table the table that new rows will be inserted into.
- * @param array $columns the column data (name => value) to be inserted into the table.
- * @return Command the command object itself
- */
- public function insert($table, $columns)
- {
- $params = [];
- $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
- /**
- * Creates a batch INSERT command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [
- * ['Tom', 30],
- * ['Jane', 20],
- * ['Linda', 25],
- * ])->execute();
- * ~~~
- *
- * Note that the values in each row must match the corresponding column names.
- *
- * @param string $table the table that new rows will be inserted into.
- * @param array $columns the column names
- * @param array $rows the rows to be batch inserted into the table
- * @return Command the command object itself
- */
- public function batchInsert($table, $columns, $rows)
- {
- $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows);
- return $this->setSql($sql);
- }
-
- /**
- * Creates an UPDATE command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute();
- * ~~~
- *
- * The method will properly escape the column names and bind the values to be updated.
- *
- * Note that the created command is not executed until [[execute()]] is called.
- *
- * @param string $table the table to be updated.
- * @param array $columns the column data (name => value) to be updated.
- * @param string|array $condition the condition that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify condition.
- * @param array $params the parameters to be bound to the command
- * @return Command the command object itself
- */
- public function update($table, $columns, $condition = '', $params = [])
- {
- $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
- /**
- * Creates a DELETE command.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->delete('tbl_user', 'status = 0')->execute();
- * ~~~
- *
- * The method will properly escape the table and column names.
- *
- * Note that the created command is not executed until [[execute()]] is called.
- *
- * @param string $table the table where the data will be deleted from.
- * @param string|array $condition the condition that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify condition.
- * @param array $params the parameters to be bound to the command
- * @return Command the command object itself
- */
- public function delete($table, $condition = '', $params = [])
- {
- $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
- return $this->setSql($sql)->bindValues($params);
- }
-
-
- /**
- * Creates a SQL command for creating a new DB table.
- *
- * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
- * where name stands for a column name which will be properly quoted by the method, and definition
- * stands for the column type which can contain an abstract DB type.
- * The method [[QueryBuilder::getColumnType()]] will be called
- * to convert the abstract column types to physical ones. For example, `string` will be converted
- * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
- *
- * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
- * inserted into the generated SQL.
- *
- * @param string $table the name of the table to be created. The name will be properly quoted by the method.
- * @param array $columns the columns (name => definition) in the new table.
- * @param string $options additional SQL fragment that will be appended to the generated SQL.
- * @return Command the command object itself
- */
- public function createTable($table, $columns, $options = null)
- {
- $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for renaming a DB table.
- * @param string $table the table to be renamed. The name will be properly quoted by the method.
- * @param string $newName the new table name. The name will be properly quoted by the method.
- * @return Command the command object itself
- */
- public function renameTable($table, $newName)
- {
- $sql = $this->db->getQueryBuilder()->renameTable($table, $newName);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for dropping a DB table.
- * @param string $table the table to be dropped. The name will be properly quoted by the method.
- * @return Command the command object itself
- */
- public function dropTable($table)
- {
- $sql = $this->db->getQueryBuilder()->dropTable($table);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for truncating a DB table.
- * @param string $table the table to be truncated. The name will be properly quoted by the method.
- * @return Command the command object itself
- */
- public function truncateTable($table)
- {
- $sql = $this->db->getQueryBuilder()->truncateTable($table);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for adding a new DB column.
- * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
- * @param string $column the name of the new column. The name will be properly quoted by the method.
- * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
- * to convert the give column type to the physical one. For example, `string` will be converted
- * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
- * @return Command the command object itself
- */
- public function addColumn($table, $column, $type)
- {
- $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for dropping a DB column.
- * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
- * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
- * @return Command the command object itself
- */
- public function dropColumn($table, $column)
- {
- $sql = $this->db->getQueryBuilder()->dropColumn($table, $column);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for renaming a column.
- * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
- * @param string $oldName the old name of the column. The name will be properly quoted by the method.
- * @param string $newName the new name of the column. The name will be properly quoted by the method.
- * @return Command the command object itself
- */
- public function renameColumn($table, $oldName, $newName)
- {
- $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for changing the definition of a column.
- * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
- * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
- * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
- * to convert the give column type to the physical one. For example, `string` will be converted
- * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
- * @return Command the command object itself
- */
- public function alterColumn($table, $column, $type)
- {
- $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for adding a primary key constraint to an existing table.
- * The method will properly quote the table and column names.
- * @param string $name the name of the primary key constraint.
- * @param string $table the table that the primary key constraint will be added to.
- * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
- * @return Command the command object itself.
- */
- public function addPrimaryKey($name, $table, $columns)
- {
- $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for removing a primary key constraint to an existing table.
- * @param string $name the name of the primary key constraint to be removed.
- * @param string $table the table that the primary key constraint will be removed from.
- * @return Command the command object itself
- */
- public function dropPrimaryKey($name, $table)
- {
- $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for adding a foreign key constraint to an existing table.
- * The method will properly quote the table and column names.
- * @param string $name the name of the foreign key constraint.
- * @param string $table the table that the foreign key constraint will be added to.
- * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
- * @param string $refTable the table that the foreign key references to.
- * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
- * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- * @return Command the command object itself
- */
- public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
- {
- $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for dropping a foreign key constraint.
- * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
- * @return Command the command object itself
- */
- public function dropForeignKey($name, $table)
- {
- $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for creating a new index.
- * @param string $name the name of the index. The name will be properly quoted by the method.
- * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
- * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
- * by commas. The column names will be properly quoted by the method.
- * @param boolean $unique whether to add UNIQUE constraint on the created index.
- * @return Command the command object itself
- */
- public function createIndex($name, $table, $columns, $unique = false)
- {
- $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for dropping an index.
- * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
- * @return Command the command object itself
- */
- public function dropIndex($name, $table)
- {
- $sql = $this->db->getQueryBuilder()->dropIndex($name, $table);
- return $this->setSql($sql);
- }
-
- /**
- * Creates a SQL command for resetting the sequence value of a table's primary key.
- * The sequence will be reset such that the primary key of the next new row inserted
- * will have the specified value or 1.
- * @param string $table the name of the table whose primary key sequence will be reset
- * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
- * the next new row's primary key will have a value 1.
- * @return Command the command object itself
- * @throws NotSupportedException if this is not supported by the underlying DBMS
- */
- public function resetSequence($table, $value = null)
- {
- $sql = $this->db->getQueryBuilder()->resetSequence($table, $value);
- return $this->setSql($sql);
- }
-
- /**
- * Builds a SQL command for enabling or disabling integrity check.
- * @param boolean $check whether to turn on or off the integrity check.
- * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current
- * or default schema.
- * @param string $table the table name.
- * @return Command the command object itself
- * @throws NotSupportedException if this is not supported by the underlying DBMS
- */
- public function checkIntegrity($check = true, $schema = '', $table = '')
- {
- $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table);
- return $this->setSql($sql);
- }
+ /**
+ * @var Connection the DB connection that this command is associated with
+ */
+ public $db;
+ /**
+ * @var \PDOStatement the PDOStatement object that this command is associated with
+ */
+ public $pdoStatement;
+ /**
+ * @var integer the default fetch mode for this command.
+ * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
+ */
+ public $fetchMode = \PDO::FETCH_ASSOC;
+ /**
+ * @var array the parameters (name => value) that are bound to the current PDO statement.
+ * This property is maintained by methods such as [[bindValue()]].
+ * Do not modify it directly.
+ */
+ public $params = [];
+ /**
+ * @var string the SQL statement that this command represents
+ */
+ private $_sql;
+
+ /**
+ * Returns the SQL statement for this command.
+ * @return string the SQL statement to be executed
+ */
+ public function getSql()
+ {
+ return $this->_sql;
+ }
+
+ /**
+ * Specifies the SQL statement to be executed.
+ * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well.
+ * @param string $sql the SQL statement to be set.
+ * @return static this command instance
+ */
+ public function setSql($sql)
+ {
+ if ($sql !== $this->_sql) {
+ $this->cancel();
+ $this->_sql = $this->db->quoteSql($sql);
+ $this->params = [];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
+ * Note that the return value of this method should mainly be used for logging purpose.
+ * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
+ * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]].
+ */
+ public function getRawSql()
+ {
+ if (empty($this->params)) {
+ return $this->_sql;
+ } else {
+ $params = [];
+ foreach ($this->params as $name => $value) {
+ if (is_string($value)) {
+ $params[$name] = $this->db->quoteValue($value);
+ } elseif ($value === null) {
+ $params[$name] = 'NULL';
+ } else {
+ $params[$name] = $value;
+ }
+ }
+ if (isset($params[1])) {
+ $sql = '';
+ foreach (explode('?', $this->_sql) as $i => $part) {
+ $sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
+ }
+
+ return $sql;
+ } else {
+ return strtr($this->_sql, $params);
+ }
+ }
+ }
+
+ /**
+ * Prepares the SQL statement to be executed.
+ * For complex SQL statement that is to be executed multiple times,
+ * this may improve performance.
+ * For SQL statement with binding parameters, this method is invoked
+ * automatically.
+ * @throws Exception if there is any DB error
+ */
+ public function prepare()
+ {
+ if ($this->pdoStatement == null) {
+ $sql = $this->getSql();
+ try {
+ $this->pdoStatement = $this->db->pdo->prepare($sql);
+ } catch (\Exception $e) {
+ $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
+ $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
+ throw new Exception($message, $errorInfo, (int) $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Cancels the execution of the SQL statement.
+ * This method mainly sets [[pdoStatement]] to be null.
+ */
+ public function cancel()
+ {
+ $this->pdoStatement = null;
+ }
+
+ /**
+ * Binds a parameter to the SQL statement to be executed.
+ * @param string|integer $name parameter identifier. For a prepared statement
+ * using named placeholders, this will be a parameter name of
+ * the form `:name`. For a prepared statement using question mark
+ * placeholders, this will be the 1-indexed position of the parameter.
+ * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter
+ * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
+ * @param integer $length length of the data type
+ * @param mixed $driverOptions the driver-specific options
+ * @return static the current command being executed
+ * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php
+ */
+ public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
+ {
+ $this->prepare();
+ if ($dataType === null) {
+ $dataType = $this->db->getSchema()->getPdoType($value);
+ }
+ if ($length === null) {
+ $this->pdoStatement->bindParam($name, $value, $dataType);
+ } elseif ($driverOptions === null) {
+ $this->pdoStatement->bindParam($name, $value, $dataType, $length);
+ } else {
+ $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
+ }
+ $this->params[$name] =& $value;
+
+ return $this;
+ }
+
+ /**
+ * Binds a value to a parameter.
+ * @param string|integer $name Parameter identifier. For a prepared statement
+ * using named placeholders, this will be a parameter name of
+ * the form `:name`. For a prepared statement using question mark
+ * placeholders, this will be the 1-indexed position of the parameter.
+ * @param mixed $value The value to bind to the parameter
+ * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
+ * @return static the current command being executed
+ * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php
+ */
+ public function bindValue($name, $value, $dataType = null)
+ {
+ $this->prepare();
+ if ($dataType === null) {
+ $dataType = $this->db->getSchema()->getPdoType($value);
+ }
+ $this->pdoStatement->bindValue($name, $value, $dataType);
+ $this->params[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Binds a list of values to the corresponding parameters.
+ * This is similar to [[bindValue()]] except that it binds multiple values at a time.
+ * Note that the SQL data type of each value is determined by its PHP type.
+ * @param array $values the values to be bound. This must be given in terms of an associative
+ * array with array keys being the parameter names, and array values the corresponding parameter values,
+ * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined
+ * by its PHP type. You may explicitly specify the PDO type by using an array: `[value, type]`,
+ * e.g. `[':name' => 'John', ':profile' => [$profile, \PDO::PARAM_LOB]]`.
+ * @return static the current command being executed
+ */
+ public function bindValues($values)
+ {
+ if (!empty($values)) {
+ $this->prepare();
+ foreach ($values as $name => $value) {
+ if (is_array($value)) {
+ $type = $value[1];
+ $value = $value[0];
+ } else {
+ $type = $this->db->getSchema()->getPdoType($value);
+ }
+ $this->pdoStatement->bindValue($name, $value, $type);
+ $this->params[$name] = $value;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Executes the SQL statement.
+ * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
+ * No result set will be returned.
+ * @return integer number of rows affected by the execution.
+ * @throws Exception execution failed
+ */
+ public function execute()
+ {
+ $sql = $this->getSql();
+
+ $rawSql = $this->getRawSql();
+
+ Yii::info($rawSql, __METHOD__);
+
+ if ($sql == '') {
+ return 0;
+ }
+
+ $token = $rawSql;
+ try {
+ Yii::beginProfile($token, __METHOD__);
+
+ $this->prepare();
+ $this->pdoStatement->execute();
+ $n = $this->pdoStatement->rowCount();
+
+ Yii::endProfile($token, __METHOD__);
+
+ return $n;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ if ($e instanceof Exception) {
+ throw $e;
+ } else {
+ $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
+ $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
+ throw new Exception($message, $errorInfo, (int) $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Executes the SQL statement and returns query result.
+ * This method is for executing a SQL query that returns result set, such as `SELECT`.
+ * @return DataReader the reader object for fetching the query result
+ * @throws Exception execution failed
+ */
+ public function query()
+ {
+ return $this->queryInternal('');
+ }
+
+ /**
+ * Executes the SQL statement and returns ALL rows at once.
+ * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
+ * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
+ * @return array all rows of the query result. Each array element is an array representing a row of data.
+ * An empty array is returned if the query results in nothing.
+ * @throws Exception execution failed
+ */
+ public function queryAll($fetchMode = null)
+ {
+ return $this->queryInternal('fetchAll', $fetchMode);
+ }
+
+ /**
+ * Executes the SQL statement and returns the first row of the result.
+ * This method is best used when only the first row of result is needed for a query.
+ * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
+ * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ * @throws Exception execution failed
+ */
+ public function queryOne($fetchMode = null)
+ {
+ return $this->queryInternal('fetch', $fetchMode);
+ }
+
+ /**
+ * Executes the SQL statement and returns the value of the first column in the first row of data.
+ * This method is best used when only a single value is needed for a query.
+ * @return string|boolean the value of the first column in the first row of the query result.
+ * False is returned if there is no value.
+ * @throws Exception execution failed
+ */
+ public function queryScalar()
+ {
+ $result = $this->queryInternal('fetchColumn', 0);
+ if (is_resource($result) && get_resource_type($result) === 'stream') {
+ return stream_get_contents($result);
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * Executes the SQL statement and returns the first column of the result.
+ * This method is best used when only the first column of result (i.e. the first element in each row)
+ * is needed for a query.
+ * @return array the first column of the query result. Empty array is returned if the query results in nothing.
+ * @throws Exception execution failed
+ */
+ public function queryColumn()
+ {
+ return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN);
+ }
+
+ /**
+ * Performs the actual DB query of a SQL statement.
+ * @param string $method method of PDOStatement to be called
+ * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
+ * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
+ * @return mixed the method execution result
+ * @throws Exception if the query causes any problem
+ */
+ private function queryInternal($method, $fetchMode = null)
+ {
+ $db = $this->db;
+ $rawSql = $this->getRawSql();
+
+ Yii::info($rawSql, 'yii\db\Command::query');
+
+ /** @var \yii\caching\Cache $cache */
+ if ($db->enableQueryCache && $method !== '') {
+ $cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache;
+ }
+
+ if (isset($cache) && $cache instanceof Cache) {
+ $cacheKey = [
+ __CLASS__,
+ $method,
+ $db->dsn,
+ $db->username,
+ $rawSql,
+ ];
+ if (($result = $cache->get($cacheKey)) !== false) {
+ Yii::trace('Query result served from cache', 'yii\db\Command::query');
+
+ return $result;
+ }
+ }
+
+ $token = $rawSql;
+ try {
+ Yii::beginProfile($token, 'yii\db\Command::query');
+
+ $this->prepare();
+ $this->pdoStatement->execute();
+
+ if ($method === '') {
+ $result = new DataReader($this);
+ } else {
+ if ($fetchMode === null) {
+ $fetchMode = $this->fetchMode;
+ }
+ $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
+ $this->pdoStatement->closeCursor();
+ }
+
+ Yii::endProfile($token, 'yii\db\Command::query');
+
+ if (isset($cache, $cacheKey) && $cache instanceof Cache) {
+ $cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
+ Yii::trace('Saved query result in cache', 'yii\db\Command::query');
+ }
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, 'yii\db\Command::query');
+ if ($e instanceof Exception) {
+ throw $e;
+ } else {
+ $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
+ $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
+ throw new Exception($message, $errorInfo, (int) $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Creates an INSERT command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->insert('tbl_user', [
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * ])->execute();
+ * ~~~
+ *
+ * The method will properly escape the column names, and bind the values to be inserted.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column data (name => value) to be inserted into the table.
+ * @return Command the command object itself
+ */
+ public function insert($table, $columns)
+ {
+ $params = [];
+ $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates a batch INSERT command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [
+ * ['Tom', 30],
+ * ['Jane', 20],
+ * ['Linda', 25],
+ * ])->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the table
+ * @return Command the command object itself
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates an UPDATE command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute();
+ * ~~~
+ *
+ * The method will properly escape the column names and bind the values to be updated.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $table the table to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param string|array $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the parameters to be bound to the command
+ * @return Command the command object itself
+ */
+ public function update($table, $columns, $condition = '', $params = [])
+ {
+ $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates a DELETE command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->delete('tbl_user', 'status = 0')->execute();
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $table the table where the data will be deleted from.
+ * @param string|array $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the parameters to be bound to the command
+ * @return Command the command object itself
+ */
+ public function delete($table, $condition = '', $params = [])
+ {
+ $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
+
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates a SQL command for creating a new DB table.
+ *
+ * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
+ * where name stands for a column name which will be properly quoted by the method, and definition
+ * stands for the column type which can contain an abstract DB type.
+ * The method [[QueryBuilder::getColumnType()]] will be called
+ * to convert the abstract column types to physical ones. For example, `string` will be converted
+ * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+ *
+ * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
+ * inserted into the generated SQL.
+ *
+ * @param string $table the name of the table to be created. The name will be properly quoted by the method.
+ * @param array $columns the columns (name => definition) in the new table.
+ * @param string $options additional SQL fragment that will be appended to the generated SQL.
+ * @return Command the command object itself
+ */
+ public function createTable($table, $columns, $options = null)
+ {
+ $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for renaming a DB table.
+ * @param string $table the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function renameTable($table, $newName)
+ {
+ $sql = $this->db->getQueryBuilder()->renameTable($table, $newName);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping a DB table.
+ * @param string $table the table to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropTable($table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropTable($table);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function truncateTable($table)
+ {
+ $sql = $this->db->getQueryBuilder()->truncateTable($table);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for adding a new DB column.
+ * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
+ * @param string $column the name of the new column. The name will be properly quoted by the method.
+ * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
+ * to convert the give column type to the physical one. For example, `string` will be converted
+ * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+ * @return Command the command object itself
+ */
+ public function addColumn($table, $column, $type)
+ {
+ $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropColumn($table, $column)
+ {
+ $sql = $this->db->getQueryBuilder()->dropColumn($table, $column);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
+ * to convert the give column type to the physical one. For example, `string` will be converted
+ * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+ * @return Command the command object itself
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for adding a primary key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * @return Command the command object itself.
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return Command the command object itself
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @return Command the command object itself
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropForeignKey($name, $table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for creating a new index.
+ * @param string $name the name of the index. The name will be properly quoted by the method.
+ * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+ * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas. The column names will be properly quoted by the method.
+ * @param boolean $unique whether to add UNIQUE constraint on the created index.
+ * @return Command the command object itself
+ */
+ public function createIndex($name, $table, $columns, $unique = false)
+ {
+ $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropIndex($name, $table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropIndex($name, $table);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $table the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return Command the command object itself
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function resetSequence($table, $value = null)
+ {
+ $sql = $this->db->getQueryBuilder()->resetSequence($table, $value);
+
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Builds a SQL command for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current
+ * or default schema.
+ * @param string $table the table name.
+ * @return Command the command object itself
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table);
+
+ return $this->setSql($sql);
+ }
}
diff --git a/framework/db/Connection.php b/framework/db/Connection.php
index 0580da119fe..6610363cb12 100644
--- a/framework/db/Connection.php
+++ b/framework/db/Connection.php
@@ -67,7 +67,7 @@
* $connection->createCommand($sql2)->execute();
* // ... executing other SQL statements ...
* $transaction->commit();
- * } catch(Exception $e) {
+ * } catch (Exception $e) {
* $transaction->rollBack();
* }
* ~~~
@@ -105,442 +105,445 @@
*/
class Connection extends Component
{
- /**
- * @event Event an event that is triggered after a DB connection is established
- */
- const EVENT_AFTER_OPEN = 'afterOpen';
+ /**
+ * @event Event an event that is triggered after a DB connection is established
+ */
+ const EVENT_AFTER_OPEN = 'afterOpen';
- /**
- * @var string the Data Source Name, or DSN, contains the information required to connect to the database.
- * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on
- * the format of the DSN string.
- * @see charset
- */
- public $dsn;
- /**
- * @var string the username for establishing DB connection. Defaults to `null` meaning no username to use.
- */
- public $username;
- /**
- * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use.
- */
- public $password;
- /**
- * @var array PDO attributes (name => value) that should be set when calling [[open()]]
- * to establish a DB connection. Please refer to the
- * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for
- * details about available attributes.
- */
- public $attributes;
- /**
- * @var PDO the PHP PDO instance associated with this DB connection.
- * This property is mainly managed by [[open()]] and [[close()]] methods.
- * When a DB connection is active, this property will represent a PDO instance;
- * otherwise, it will be null.
- */
- public $pdo;
- /**
- * @var boolean whether to enable schema caching.
- * Note that in order to enable truly schema caching, a valid cache component as specified
- * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
- * @see schemaCacheDuration
- * @see schemaCacheExclude
- * @see schemaCache
- */
- public $enableSchemaCache = false;
- /**
- * @var integer number of seconds that table metadata can remain valid in cache.
- * Use 0 to indicate that the cached data will never expire.
- * @see enableSchemaCache
- */
- public $schemaCacheDuration = 3600;
- /**
- * @var array list of tables whose metadata should NOT be cached. Defaults to empty array.
- * The table names may contain schema prefix, if any. Do not quote the table names.
- * @see enableSchemaCache
- */
- public $schemaCacheExclude = [];
- /**
- * @var Cache|string the cache object or the ID of the cache application component that
- * is used to cache the table metadata.
- * @see enableSchemaCache
- */
- public $schemaCache = 'cache';
- /**
- * @var boolean whether to enable query caching.
- * Note that in order to enable query caching, a valid cache component as specified
- * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
- *
- * Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on
- * and off query caching on the fly.
- * @see queryCacheDuration
- * @see queryCache
- * @see queryCacheDependency
- * @see beginCache()
- * @see endCache()
- */
- public $enableQueryCache = false;
- /**
- * @var integer number of seconds that query results can remain valid in cache.
- * Defaults to 3600, meaning 3600 seconds, or one hour.
- * Use 0 to indicate that the cached data will never expire.
- * @see enableQueryCache
- */
- public $queryCacheDuration = 3600;
- /**
- * @var \yii\caching\Dependency the dependency that will be used when saving query results into cache.
- * Defaults to null, meaning no dependency.
- * @see enableQueryCache
- */
- public $queryCacheDependency;
- /**
- * @var Cache|string the cache object or the ID of the cache application component
- * that is used for query caching.
- * @see enableQueryCache
- */
- public $queryCache = 'cache';
- /**
- * @var string the charset used for database connection. The property is only used
- * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset
- * as specified by the database.
- *
- * Note that if you're using GBK or BIG5 then it's highly recommended to
- * specify charset via DSN like 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'.
- */
- public $charset;
- /**
- * @var boolean whether to turn on prepare emulation. Defaults to false, meaning PDO
- * will use the native prepare support if available. For some databases (such as MySQL),
- * this may need to be set true so that PDO can emulate the prepare support to bypass
- * the buggy native prepare support.
- * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed.
- */
- public $emulatePrepare;
- /**
- * @var string the common prefix or suffix for table names. If a table name is given
- * as `{{%TableName}}`, then the percentage character `%` will be replaced with this
- * property value. For example, `{{%post}}` becomes `{{tbl_post}}`.
- */
- public $tablePrefix = 'tbl_';
- /**
- * @var array mapping between PDO driver names and [[Schema]] classes.
- * The keys of the array are PDO driver names while the values the corresponding
- * schema class name or configuration. Please refer to [[Yii::createObject()]] for
- * details on how to specify a configuration.
- *
- * This property is mainly used by [[getSchema()]] when fetching the database schema information.
- * You normally do not need to set this property unless you want to use your own
- * [[Schema]] class to support DBMS that is not supported by Yii.
- */
- public $schemaMap = [
- 'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
- 'mysqli' => 'yii\db\mysql\Schema', // MySQL
- 'mysql' => 'yii\db\mysql\Schema', // MySQL
- 'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
- 'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
- 'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
- 'oci' => 'yii\db\oci\Schema', // Oracle driver
- 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts
- 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
- 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID
- ];
- /**
- * @var string Custom PDO wrapper class. If not set, it will use "PDO" or "yii\db\mssql\PDO" when MSSQL is used.
- */
- public $pdoClass;
- /**
- * @var boolean whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint).
- * Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect.
- */
- public $enableSavepoint = true;
- /**
- * @var Transaction the currently active transaction
- */
- private $_transaction;
- /**
- * @var Schema the database schema
- */
- private $_schema;
+ /**
+ * @var string the Data Source Name, or DSN, contains the information required to connect to the database.
+ * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on
+ * the format of the DSN string.
+ * @see charset
+ */
+ public $dsn;
+ /**
+ * @var string the username for establishing DB connection. Defaults to `null` meaning no username to use.
+ */
+ public $username;
+ /**
+ * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use.
+ */
+ public $password;
+ /**
+ * @var array PDO attributes (name => value) that should be set when calling [[open()]]
+ * to establish a DB connection. Please refer to the
+ * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for
+ * details about available attributes.
+ */
+ public $attributes;
+ /**
+ * @var PDO the PHP PDO instance associated with this DB connection.
+ * This property is mainly managed by [[open()]] and [[close()]] methods.
+ * When a DB connection is active, this property will represent a PDO instance;
+ * otherwise, it will be null.
+ */
+ public $pdo;
+ /**
+ * @var boolean whether to enable schema caching.
+ * Note that in order to enable truly schema caching, a valid cache component as specified
+ * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
+ * @see schemaCacheDuration
+ * @see schemaCacheExclude
+ * @see schemaCache
+ */
+ public $enableSchemaCache = false;
+ /**
+ * @var integer number of seconds that table metadata can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ * @see enableSchemaCache
+ */
+ public $schemaCacheDuration = 3600;
+ /**
+ * @var array list of tables whose metadata should NOT be cached. Defaults to empty array.
+ * The table names may contain schema prefix, if any. Do not quote the table names.
+ * @see enableSchemaCache
+ */
+ public $schemaCacheExclude = [];
+ /**
+ * @var Cache|string the cache object or the ID of the cache application component that
+ * is used to cache the table metadata.
+ * @see enableSchemaCache
+ */
+ public $schemaCache = 'cache';
+ /**
+ * @var boolean whether to enable query caching.
+ * Note that in order to enable query caching, a valid cache component as specified
+ * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
+ *
+ * Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on
+ * and off query caching on the fly.
+ * @see queryCacheDuration
+ * @see queryCache
+ * @see queryCacheDependency
+ * @see beginCache()
+ * @see endCache()
+ */
+ public $enableQueryCache = false;
+ /**
+ * @var integer number of seconds that query results can remain valid in cache.
+ * Defaults to 3600, meaning 3600 seconds, or one hour.
+ * Use 0 to indicate that the cached data will never expire.
+ * @see enableQueryCache
+ */
+ public $queryCacheDuration = 3600;
+ /**
+ * @var \yii\caching\Dependency the dependency that will be used when saving query results into cache.
+ * Defaults to null, meaning no dependency.
+ * @see enableQueryCache
+ */
+ public $queryCacheDependency;
+ /**
+ * @var Cache|string the cache object or the ID of the cache application component
+ * that is used for query caching.
+ * @see enableQueryCache
+ */
+ public $queryCache = 'cache';
+ /**
+ * @var string the charset used for database connection. The property is only used
+ * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset
+ * as specified by the database.
+ *
+ * Note that if you're using GBK or BIG5 then it's highly recommended to
+ * specify charset via DSN like 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'.
+ */
+ public $charset;
+ /**
+ * @var boolean whether to turn on prepare emulation. Defaults to false, meaning PDO
+ * will use the native prepare support if available. For some databases (such as MySQL),
+ * this may need to be set true so that PDO can emulate the prepare support to bypass
+ * the buggy native prepare support.
+ * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed.
+ */
+ public $emulatePrepare;
+ /**
+ * @var string the common prefix or suffix for table names. If a table name is given
+ * as `{{%TableName}}`, then the percentage character `%` will be replaced with this
+ * property value. For example, `{{%post}}` becomes `{{tbl_post}}`.
+ */
+ public $tablePrefix = 'tbl_';
+ /**
+ * @var array mapping between PDO driver names and [[Schema]] classes.
+ * The keys of the array are PDO driver names while the values the corresponding
+ * schema class name or configuration. Please refer to [[Yii::createObject()]] for
+ * details on how to specify a configuration.
+ *
+ * This property is mainly used by [[getSchema()]] when fetching the database schema information.
+ * You normally do not need to set this property unless you want to use your own
+ * [[Schema]] class to support DBMS that is not supported by Yii.
+ */
+ public $schemaMap = [
+ 'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
+ 'mysqli' => 'yii\db\mysql\Schema', // MySQL
+ 'mysql' => 'yii\db\mysql\Schema', // MySQL
+ 'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
+ 'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
+ 'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
+ 'oci' => 'yii\db\oci\Schema', // Oracle driver
+ 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts
+ 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
+ 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID
+ ];
+ /**
+ * @var string Custom PDO wrapper class. If not set, it will use "PDO" or "yii\db\mssql\PDO" when MSSQL is used.
+ */
+ public $pdoClass;
+ /**
+ * @var boolean whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint).
+ * Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect.
+ */
+ public $enableSavepoint = true;
+ /**
+ * @var Transaction the currently active transaction
+ */
+ private $_transaction;
+ /**
+ * @var Schema the database schema
+ */
+ private $_schema;
+ /**
+ * Returns a value indicating whether the DB connection is established.
+ * @return boolean whether the DB connection is established
+ */
+ public function getIsActive()
+ {
+ return $this->pdo !== null;
+ }
- /**
- * Returns a value indicating whether the DB connection is established.
- * @return boolean whether the DB connection is established
- */
- public function getIsActive()
- {
- return $this->pdo !== null;
- }
+ /**
+ * Turns on query caching.
+ * This method is provided as a shortcut to setting two properties that are related
+ * with query caching: [[queryCacheDuration]] and [[queryCacheDependency]].
+ * @param integer $duration the number of seconds that query results may remain valid in cache.
+ * If not set, it will use the value of [[queryCacheDuration]]. See [[queryCacheDuration]] for more details.
+ * @param \yii\caching\Dependency $dependency the dependency for the cached query result.
+ * See [[queryCacheDependency]] for more details.
+ */
+ public function beginCache($duration = null, $dependency = null)
+ {
+ $this->enableQueryCache = true;
+ if ($duration !== null) {
+ $this->queryCacheDuration = $duration;
+ }
+ $this->queryCacheDependency = $dependency;
+ }
- /**
- * Turns on query caching.
- * This method is provided as a shortcut to setting two properties that are related
- * with query caching: [[queryCacheDuration]] and [[queryCacheDependency]].
- * @param integer $duration the number of seconds that query results may remain valid in cache.
- * If not set, it will use the value of [[queryCacheDuration]]. See [[queryCacheDuration]] for more details.
- * @param \yii\caching\Dependency $dependency the dependency for the cached query result.
- * See [[queryCacheDependency]] for more details.
- */
- public function beginCache($duration = null, $dependency = null)
- {
- $this->enableQueryCache = true;
- if ($duration !== null) {
- $this->queryCacheDuration = $duration;
- }
- $this->queryCacheDependency = $dependency;
- }
+ /**
+ * Turns off query caching.
+ */
+ public function endCache()
+ {
+ $this->enableQueryCache = false;
+ }
- /**
- * Turns off query caching.
- */
- public function endCache()
- {
- $this->enableQueryCache = false;
- }
+ /**
+ * Establishes a DB connection.
+ * It does nothing if a DB connection has already been established.
+ * @throws Exception if connection fails
+ */
+ public function open()
+ {
+ if ($this->pdo === null) {
+ if (empty($this->dsn)) {
+ throw new InvalidConfigException('Connection::dsn cannot be empty.');
+ }
+ $token = 'Opening DB connection: ' . $this->dsn;
+ try {
+ Yii::trace($token, __METHOD__);
+ Yii::beginProfile($token, __METHOD__);
+ $this->pdo = $this->createPdoInstance();
+ $this->initConnection();
+ Yii::endProfile($token, __METHOD__);
+ } catch (\PDOException $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
+ }
+ }
+ }
- /**
- * Establishes a DB connection.
- * It does nothing if a DB connection has already been established.
- * @throws Exception if connection fails
- */
- public function open()
- {
- if ($this->pdo === null) {
- if (empty($this->dsn)) {
- throw new InvalidConfigException('Connection::dsn cannot be empty.');
- }
- $token = 'Opening DB connection: ' . $this->dsn;
- try {
- Yii::trace($token, __METHOD__);
- Yii::beginProfile($token, __METHOD__);
- $this->pdo = $this->createPdoInstance();
- $this->initConnection();
- Yii::endProfile($token, __METHOD__);
- } catch (\PDOException $e) {
- Yii::endProfile($token, __METHOD__);
- throw new Exception($e->getMessage(), $e->errorInfo, (int)$e->getCode(), $e);
- }
- }
- }
+ /**
+ * Closes the currently active DB connection.
+ * It does nothing if the connection is already closed.
+ */
+ public function close()
+ {
+ if ($this->pdo !== null) {
+ Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
+ $this->pdo = null;
+ $this->_schema = null;
+ $this->_transaction = null;
+ }
+ }
- /**
- * Closes the currently active DB connection.
- * It does nothing if the connection is already closed.
- */
- public function close()
- {
- if ($this->pdo !== null) {
- Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
- $this->pdo = null;
- $this->_schema = null;
- $this->_transaction = null;
- }
- }
+ /**
+ * Creates the PDO instance.
+ * This method is called by [[open]] to establish a DB connection.
+ * The default implementation will create a PHP PDO instance.
+ * You may override this method if the default PDO needs to be adapted for certain DBMS.
+ * @return PDO the pdo instance
+ */
+ protected function createPdoInstance()
+ {
+ $pdoClass = $this->pdoClass;
+ if ($pdoClass === null) {
+ $pdoClass = 'PDO';
+ if (($pos = strpos($this->dsn, ':')) !== false) {
+ $driver = strtolower(substr($this->dsn, 0, $pos));
+ if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') {
+ $pdoClass = 'yii\db\mssql\PDO';
+ }
+ }
+ }
- /**
- * Creates the PDO instance.
- * This method is called by [[open]] to establish a DB connection.
- * The default implementation will create a PHP PDO instance.
- * You may override this method if the default PDO needs to be adapted for certain DBMS.
- * @return PDO the pdo instance
- */
- protected function createPdoInstance()
- {
- $pdoClass = $this->pdoClass;
- if ($pdoClass === null) {
- $pdoClass = 'PDO';
- if (($pos = strpos($this->dsn, ':')) !== false) {
- $driver = strtolower(substr($this->dsn, 0, $pos));
- if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') {
- $pdoClass = 'yii\db\mssql\PDO';
- }
- }
- }
+ return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes);
+ }
- return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes);
- }
+ /**
+ * Initializes the DB connection.
+ * This method is invoked right after the DB connection is established.
+ * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
+ * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
+ * It then triggers an [[EVENT_AFTER_OPEN]] event.
+ */
+ protected function initConnection()
+ {
+ $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
+ $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
+ }
+ if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'])) {
+ $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
+ }
+ $this->trigger(self::EVENT_AFTER_OPEN);
+ }
- /**
- * Initializes the DB connection.
- * This method is invoked right after the DB connection is established.
- * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
- * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
- * It then triggers an [[EVENT_AFTER_OPEN]] event.
- */
- protected function initConnection()
- {
- $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
- $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
- }
- if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'])) {
- $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
- }
- $this->trigger(self::EVENT_AFTER_OPEN);
- }
+ /**
+ * Creates a command for execution.
+ * @param string $sql the SQL statement to be executed
+ * @param array $params the parameters to be bound to the SQL statement
+ * @return Command the DB command
+ */
+ public function createCommand($sql = null, $params = [])
+ {
+ $this->open();
+ $command = new Command([
+ 'db' => $this,
+ 'sql' => $sql,
+ ]);
- /**
- * Creates a command for execution.
- * @param string $sql the SQL statement to be executed
- * @param array $params the parameters to be bound to the SQL statement
- * @return Command the DB command
- */
- public function createCommand($sql = null, $params = [])
- {
- $this->open();
- $command = new Command([
- 'db' => $this,
- 'sql' => $sql,
- ]);
- return $command->bindValues($params);
- }
+ return $command->bindValues($params);
+ }
- /**
- * Returns the currently active transaction.
- * @return Transaction the currently active transaction. Null if no active transaction.
- */
- public function getTransaction()
- {
- return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
- }
+ /**
+ * Returns the currently active transaction.
+ * @return Transaction the currently active transaction. Null if no active transaction.
+ */
+ public function getTransaction()
+ {
+ return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
+ }
- /**
- * Starts a transaction.
- * @return Transaction the transaction initiated
- */
- public function beginTransaction()
- {
- $this->open();
+ /**
+ * Starts a transaction.
+ * @return Transaction the transaction initiated
+ */
+ public function beginTransaction()
+ {
+ $this->open();
- if (($transaction = $this->getTransaction()) === null) {
- $transaction = $this->_transaction = new Transaction(['db' => $this]);
- }
- $transaction->begin();
- return $transaction;
- }
+ if (($transaction = $this->getTransaction()) === null) {
+ $transaction = $this->_transaction = new Transaction(['db' => $this]);
+ }
+ $transaction->begin();
- /**
- * Returns the schema information for the database opened by this connection.
- * @return Schema the schema information for the database opened by this connection.
- * @throws NotSupportedException if there is no support for the current driver type
- */
- public function getSchema()
- {
- if ($this->_schema !== null) {
- return $this->_schema;
- } else {
- $driver = $this->getDriverName();
- if (isset($this->schemaMap[$driver])) {
- $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
- $config['db'] = $this;
- return $this->_schema = Yii::createObject($config);
- } else {
- throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
- }
- }
- }
+ return $transaction;
+ }
- /**
- * Returns the query builder for the current DB connection.
- * @return QueryBuilder the query builder for the current DB connection.
- */
- public function getQueryBuilder()
- {
- return $this->getSchema()->getQueryBuilder();
- }
+ /**
+ * Returns the schema information for the database opened by this connection.
+ * @return Schema the schema information for the database opened by this connection.
+ * @throws NotSupportedException if there is no support for the current driver type
+ */
+ public function getSchema()
+ {
+ if ($this->_schema !== null) {
+ return $this->_schema;
+ } else {
+ $driver = $this->getDriverName();
+ if (isset($this->schemaMap[$driver])) {
+ $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
+ $config['db'] = $this;
- /**
- * Obtains the schema information for the named table.
- * @param string $name table name.
- * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
- * @return TableSchema table schema information. Null if the named table does not exist.
- */
- public function getTableSchema($name, $refresh = false)
- {
- return $this->getSchema()->getTableSchema($name, $refresh);
- }
+ return $this->_schema = Yii::createObject($config);
+ } else {
+ throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
+ }
+ }
+ }
- /**
- * Returns the ID of the last inserted row or sequence value.
- * @param string $sequenceName name of the sequence object (required by some DBMS)
- * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
- * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
- */
- public function getLastInsertID($sequenceName = '')
- {
- return $this->getSchema()->getLastInsertID($sequenceName);
- }
+ /**
+ * Returns the query builder for the current DB connection.
+ * @return QueryBuilder the query builder for the current DB connection.
+ */
+ public function getQueryBuilder()
+ {
+ return $this->getSchema()->getQueryBuilder();
+ }
- /**
- * Quotes a string value for use in a query.
- * Note that if the parameter is not a string, it will be returned without change.
- * @param string $str string to be quoted
- * @return string the properly quoted string
- * @see http://www.php.net/manual/en/function.PDO-quote.php
- */
- public function quoteValue($str)
- {
- return $this->getSchema()->quoteValue($str);
- }
+ /**
+ * Obtains the schema information for the named table.
+ * @param string $name table name.
+ * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
+ * @return TableSchema table schema information. Null if the named table does not exist.
+ */
+ public function getTableSchema($name, $refresh = false)
+ {
+ return $this->getSchema()->getTableSchema($name, $refresh);
+ }
- /**
- * Quotes a table name for use in a query.
- * If the table name contains schema prefix, the prefix will also be properly quoted.
- * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
- * then this method will do nothing.
- * @param string $name table name
- * @return string the properly quoted table name
- */
- public function quoteTableName($name)
- {
- return $this->getSchema()->quoteTableName($name);
- }
+ /**
+ * Returns the ID of the last inserted row or sequence value.
+ * @param string $sequenceName name of the sequence object (required by some DBMS)
+ * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
+ * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
+ */
+ public function getLastInsertID($sequenceName = '')
+ {
+ return $this->getSchema()->getLastInsertID($sequenceName);
+ }
- /**
- * Quotes a column name for use in a query.
- * If the column name contains prefix, the prefix will also be properly quoted.
- * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
- * then this method will do nothing.
- * @param string $name column name
- * @return string the properly quoted column name
- */
- public function quoteColumnName($name)
- {
- return $this->getSchema()->quoteColumnName($name);
- }
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.PDO-quote.php
+ */
+ public function quoteValue($str)
+ {
+ return $this->getSchema()->quoteValue($str);
+ }
- /**
- * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
- * Tokens enclosed within double curly brackets are treated as table names, while
- * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
- * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
- * with [[tablePrefix]].
- * @param string $sql the SQL to be quoted
- * @return string the quoted SQL
- */
- public function quoteSql($sql)
- {
- return preg_replace_callback('/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
- function ($matches) {
- if (isset($matches[3])) {
- return $this->quoteColumnName($matches[3]);
- } else {
- return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
- }
- }, $sql);
- }
+ /**
+ * Quotes a table name for use in a query.
+ * If the table name contains schema prefix, the prefix will also be properly quoted.
+ * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
+ * then this method will do nothing.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteTableName($name)
+ {
+ return $this->getSchema()->quoteTableName($name);
+ }
- /**
- * Returns the name of the DB driver for the current [[dsn]].
- * @return string name of the DB driver
- */
- public function getDriverName()
- {
- if (($pos = strpos($this->dsn, ':')) !== false) {
- return strtolower(substr($this->dsn, 0, $pos));
- } else {
- $this->open();
- return strtolower($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
- }
- }
+ /**
+ * Quotes a column name for use in a query.
+ * If the column name contains prefix, the prefix will also be properly quoted.
+ * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
+ * then this method will do nothing.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteColumnName($name)
+ {
+ return $this->getSchema()->quoteColumnName($name);
+ }
+
+ /**
+ * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
+ * Tokens enclosed within double curly brackets are treated as table names, while
+ * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
+ * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
+ * with [[tablePrefix]].
+ * @param string $sql the SQL to be quoted
+ * @return string the quoted SQL
+ */
+ public function quoteSql($sql)
+ {
+ return preg_replace_callback('/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
+ function ($matches) {
+ if (isset($matches[3])) {
+ return $this->quoteColumnName($matches[3]);
+ } else {
+ return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
+ }
+ }, $sql);
+ }
+
+ /**
+ * Returns the name of the DB driver for the current [[dsn]].
+ * @return string name of the DB driver
+ */
+ public function getDriverName()
+ {
+ if (($pos = strpos($this->dsn, ':')) !== false) {
+ return strtolower(substr($this->dsn, 0, $pos));
+ } else {
+ $this->open();
+
+ return strtolower($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
+ }
+ }
}
diff --git a/framework/db/DataReader.php b/framework/db/DataReader.php
index 213db52b6e2..bec616c9c5a 100644
--- a/framework/db/DataReader.php
+++ b/framework/db/DataReader.php
@@ -49,216 +49,217 @@
*/
class DataReader extends \yii\base\Object implements \Iterator, \Countable
{
- /**
- * @var \PDOStatement the PDOStatement associated with the command
- */
- private $_statement;
- private $_closed = false;
- private $_row;
- private $_index = -1;
+ /**
+ * @var \PDOStatement the PDOStatement associated with the command
+ */
+ private $_statement;
+ private $_closed = false;
+ private $_row;
+ private $_index = -1;
- /**
- * Constructor.
- * @param Command $command the command generating the query result
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct(Command $command, $config = [])
- {
- $this->_statement = $command->pdoStatement;
- $this->_statement->setFetchMode(\PDO::FETCH_ASSOC);
- parent::__construct($config);
- }
+ /**
+ * Constructor.
+ * @param Command $command the command generating the query result
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct(Command $command, $config = [])
+ {
+ $this->_statement = $command->pdoStatement;
+ $this->_statement->setFetchMode(\PDO::FETCH_ASSOC);
+ parent::__construct($config);
+ }
- /**
- * Binds a column to a PHP variable.
- * When rows of data are being fetched, the corresponding column value
- * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND.
- * @param integer|string $column Number of the column (1-indexed) or name of the column
- * in the result set. If using the column name, be aware that the name
- * should match the case of the column, as returned by the driver.
- * @param mixed $value Name of the PHP variable to which the column will be bound.
- * @param integer $dataType Data type of the parameter
- * @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php
- */
- public function bindColumn($column, &$value, $dataType = null)
- {
- if ($dataType === null) {
- $this->_statement->bindColumn($column, $value);
- } else {
- $this->_statement->bindColumn($column, $value, $dataType);
- }
- }
+ /**
+ * Binds a column to a PHP variable.
+ * When rows of data are being fetched, the corresponding column value
+ * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND.
+ * @param integer|string $column Number of the column (1-indexed) or name of the column
+ * in the result set. If using the column name, be aware that the name
+ * should match the case of the column, as returned by the driver.
+ * @param mixed $value Name of the PHP variable to which the column will be bound.
+ * @param integer $dataType Data type of the parameter
+ * @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php
+ */
+ public function bindColumn($column, &$value, $dataType = null)
+ {
+ if ($dataType === null) {
+ $this->_statement->bindColumn($column, $value);
+ } else {
+ $this->_statement->bindColumn($column, $value, $dataType);
+ }
+ }
- /**
- * Set the default fetch mode for this statement
- * @param integer $mode fetch mode
- * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
- */
- public function setFetchMode($mode)
- {
- $params = func_get_args();
- call_user_func_array([$this->_statement, 'setFetchMode'], $params);
- }
+ /**
+ * Set the default fetch mode for this statement
+ * @param integer $mode fetch mode
+ * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
+ */
+ public function setFetchMode($mode)
+ {
+ $params = func_get_args();
+ call_user_func_array([$this->_statement, 'setFetchMode'], $params);
+ }
- /**
- * Advances the reader to the next row in a result set.
- * @return array the current row, false if no more row available
- */
- public function read()
- {
- return $this->_statement->fetch();
- }
+ /**
+ * Advances the reader to the next row in a result set.
+ * @return array the current row, false if no more row available
+ */
+ public function read()
+ {
+ return $this->_statement->fetch();
+ }
- /**
- * Returns a single column from the next row of a result set.
- * @param integer $columnIndex zero-based column index
- * @return mixed the column of the current row, false if no more rows available
- */
- public function readColumn($columnIndex)
- {
- return $this->_statement->fetchColumn($columnIndex);
- }
+ /**
+ * Returns a single column from the next row of a result set.
+ * @param integer $columnIndex zero-based column index
+ * @return mixed the column of the current row, false if no more rows available
+ */
+ public function readColumn($columnIndex)
+ {
+ return $this->_statement->fetchColumn($columnIndex);
+ }
- /**
- * Returns an object populated with the next row of data.
- * @param string $className class name of the object to be created and populated
- * @param array $fields Elements of this array are passed to the constructor
- * @return mixed the populated object, false if no more row of data available
- */
- public function readObject($className, $fields)
- {
- return $this->_statement->fetchObject($className, $fields);
- }
+ /**
+ * Returns an object populated with the next row of data.
+ * @param string $className class name of the object to be created and populated
+ * @param array $fields Elements of this array are passed to the constructor
+ * @return mixed the populated object, false if no more row of data available
+ */
+ public function readObject($className, $fields)
+ {
+ return $this->_statement->fetchObject($className, $fields);
+ }
- /**
- * Reads the whole result set into an array.
- * @return array the result set (each array element represents a row of data).
- * An empty array will be returned if the result contains no row.
- */
- public function readAll()
- {
- return $this->_statement->fetchAll();
- }
+ /**
+ * Reads the whole result set into an array.
+ * @return array the result set (each array element represents a row of data).
+ * An empty array will be returned if the result contains no row.
+ */
+ public function readAll()
+ {
+ return $this->_statement->fetchAll();
+ }
- /**
- * Advances the reader to the next result when reading the results of a batch of statements.
- * This method is only useful when there are multiple result sets
- * returned by the query. Not all DBMS support this feature.
- * @return boolean Returns true on success or false on failure.
- */
- public function nextResult()
- {
- if (($result = $this->_statement->nextRowset()) !== false) {
- $this->_index = -1;
- }
- return $result;
- }
+ /**
+ * Advances the reader to the next result when reading the results of a batch of statements.
+ * This method is only useful when there are multiple result sets
+ * returned by the query. Not all DBMS support this feature.
+ * @return boolean Returns true on success or false on failure.
+ */
+ public function nextResult()
+ {
+ if (($result = $this->_statement->nextRowset()) !== false) {
+ $this->_index = -1;
+ }
- /**
- * Closes the reader.
- * This frees up the resources allocated for executing this SQL statement.
- * Read attempts after this method call are unpredictable.
- */
- public function close()
- {
- $this->_statement->closeCursor();
- $this->_closed = true;
- }
+ return $result;
+ }
- /**
- * whether the reader is closed or not.
- * @return boolean whether the reader is closed or not.
- */
- public function getIsClosed()
- {
- return $this->_closed;
- }
+ /**
+ * Closes the reader.
+ * This frees up the resources allocated for executing this SQL statement.
+ * Read attempts after this method call are unpredictable.
+ */
+ public function close()
+ {
+ $this->_statement->closeCursor();
+ $this->_closed = true;
+ }
- /**
- * Returns the number of rows in the result set.
- * Note, most DBMS may not give a meaningful count.
- * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
- * @return integer number of rows contained in the result.
- */
- public function getRowCount()
- {
- return $this->_statement->rowCount();
- }
+ /**
+ * whether the reader is closed or not.
+ * @return boolean whether the reader is closed or not.
+ */
+ public function getIsClosed()
+ {
+ return $this->_closed;
+ }
- /**
- * Returns the number of rows in the result set.
- * This method is required by the Countable interface.
- * Note, most DBMS may not give a meaningful count.
- * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
- * @return integer number of rows contained in the result.
- */
- public function count()
- {
- return $this->getRowCount();
- }
+ /**
+ * Returns the number of rows in the result set.
+ * Note, most DBMS may not give a meaningful count.
+ * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
+ * @return integer number of rows contained in the result.
+ */
+ public function getRowCount()
+ {
+ return $this->_statement->rowCount();
+ }
- /**
- * Returns the number of columns in the result set.
- * Note, even there's no row in the reader, this still gives correct column number.
- * @return integer the number of columns in the result set.
- */
- public function getColumnCount()
- {
- return $this->_statement->columnCount();
- }
+ /**
+ * Returns the number of rows in the result set.
+ * This method is required by the Countable interface.
+ * Note, most DBMS may not give a meaningful count.
+ * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
+ * @return integer number of rows contained in the result.
+ */
+ public function count()
+ {
+ return $this->getRowCount();
+ }
- /**
- * Resets the iterator to the initial state.
- * This method is required by the interface Iterator.
- * @throws InvalidCallException if this method is invoked twice
- */
- public function rewind()
- {
- if ($this->_index < 0) {
- $this->_row = $this->_statement->fetch();
- $this->_index = 0;
- } else {
- throw new InvalidCallException('DataReader cannot rewind. It is a forward-only reader.');
- }
- }
+ /**
+ * Returns the number of columns in the result set.
+ * Note, even there's no row in the reader, this still gives correct column number.
+ * @return integer the number of columns in the result set.
+ */
+ public function getColumnCount()
+ {
+ return $this->_statement->columnCount();
+ }
- /**
- * Returns the index of the current row.
- * This method is required by the interface Iterator.
- * @return integer the index of the current row.
- */
- public function key()
- {
- return $this->_index;
- }
+ /**
+ * Resets the iterator to the initial state.
+ * This method is required by the interface Iterator.
+ * @throws InvalidCallException if this method is invoked twice
+ */
+ public function rewind()
+ {
+ if ($this->_index < 0) {
+ $this->_row = $this->_statement->fetch();
+ $this->_index = 0;
+ } else {
+ throw new InvalidCallException('DataReader cannot rewind. It is a forward-only reader.');
+ }
+ }
- /**
- * Returns the current row.
- * This method is required by the interface Iterator.
- * @return mixed the current row.
- */
- public function current()
- {
- return $this->_row;
- }
+ /**
+ * Returns the index of the current row.
+ * This method is required by the interface Iterator.
+ * @return integer the index of the current row.
+ */
+ public function key()
+ {
+ return $this->_index;
+ }
- /**
- * Moves the internal pointer to the next row.
- * This method is required by the interface Iterator.
- */
- public function next()
- {
- $this->_row = $this->_statement->fetch();
- $this->_index++;
- }
+ /**
+ * Returns the current row.
+ * This method is required by the interface Iterator.
+ * @return mixed the current row.
+ */
+ public function current()
+ {
+ return $this->_row;
+ }
- /**
- * Returns whether there is a row of data at current position.
- * This method is required by the interface Iterator.
- * @return boolean whether there is a row of data at current position.
- */
- public function valid()
- {
- return $this->_row !== false;
- }
+ /**
+ * Moves the internal pointer to the next row.
+ * This method is required by the interface Iterator.
+ */
+ public function next()
+ {
+ $this->_row = $this->_statement->fetch();
+ $this->_index++;
+ }
+
+ /**
+ * Returns whether there is a row of data at current position.
+ * This method is required by the interface Iterator.
+ * @return boolean whether there is a row of data at current position.
+ */
+ public function valid()
+ {
+ return $this->_row !== false;
+ }
}
diff --git a/framework/db/Exception.php b/framework/db/Exception.php
index d5cafeff746..44949f65c02 100644
--- a/framework/db/Exception.php
+++ b/framework/db/Exception.php
@@ -15,36 +15,36 @@
*/
class Exception extends \yii\base\Exception
{
- /**
- * @var array the error info provided by a PDO exception. This is the same as returned
- * by [PDO::errorInfo](http://www.php.net/manual/en/pdo.errorinfo.php).
- */
- public $errorInfo = [];
+ /**
+ * @var array the error info provided by a PDO exception. This is the same as returned
+ * by [PDO::errorInfo](http://www.php.net/manual/en/pdo.errorinfo.php).
+ */
+ public $errorInfo = [];
- /**
- * Constructor.
- * @param string $message PDO error message
- * @param array $errorInfo PDO error info
- * @param integer $code PDO error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message, $errorInfo = [], $code = 0, \Exception $previous = null)
- {
- $this->errorInfo = $errorInfo;
- parent::__construct($message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message PDO error message
+ * @param array $errorInfo PDO error info
+ * @param integer $code PDO error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message, $errorInfo = [], $code = 0, \Exception $previous = null)
+ {
+ $this->errorInfo = $errorInfo;
+ parent::__construct($message, $code, $previous);
+ }
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Database Exception';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Database Exception';
+ }
- public function __toString()
- {
- return parent::__toString() . PHP_EOL
- . 'Additional Information:' . PHP_EOL . print_r($this->errorInfo, true);
- }
+ public function __toString()
+ {
+ return parent::__toString() . PHP_EOL
+ . 'Additional Information:' . PHP_EOL . print_r($this->errorInfo, true);
+ }
}
diff --git a/framework/db/Expression.php b/framework/db/Expression.php
index 7fa9124d72c..0e2130e3326 100644
--- a/framework/db/Expression.php
+++ b/framework/db/Expression.php
@@ -25,36 +25,36 @@
*/
class Expression extends \yii\base\Object
{
- /**
- * @var string the DB expression
- */
- public $expression;
- /**
- * @var array list of parameters that should be bound for this expression.
- * The keys are placeholders appearing in [[expression]] and the values
- * are the corresponding parameter values.
- */
- public $params = [];
+ /**
+ * @var string the DB expression
+ */
+ public $expression;
+ /**
+ * @var array list of parameters that should be bound for this expression.
+ * The keys are placeholders appearing in [[expression]] and the values
+ * are the corresponding parameter values.
+ */
+ public $params = [];
- /**
- * Constructor.
- * @param string $expression the DB expression
- * @param array $params parameters
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($expression, $params = [], $config = [])
- {
- $this->expression = $expression;
- $this->params = $params;
- parent::__construct($config);
- }
+ /**
+ * Constructor.
+ * @param string $expression the DB expression
+ * @param array $params parameters
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($expression, $params = [], $config = [])
+ {
+ $this->expression = $expression;
+ $this->params = $params;
+ parent::__construct($config);
+ }
- /**
- * String magic method
- * @return string the DB expression
- */
- public function __toString()
- {
- return $this->expression;
- }
+ /**
+ * String magic method
+ * @return string the DB expression
+ */
+ public function __toString()
+ {
+ return $this->expression;
+ }
}
diff --git a/framework/db/Migration.php b/framework/db/Migration.php
index 26659d1d4d3..89927a7c166 100644
--- a/framework/db/Migration.php
+++ b/framework/db/Migration.php
@@ -35,381 +35,387 @@
*/
class Migration extends \yii\base\Component
{
- /**
- * @var Connection the database connection that this migration should work with.
- * If not set, it will be initialized as the 'db' application component.
- */
- public $db;
-
- /**
- * Initializes the migration.
- * This method will set [[db]] to be the 'db' application component, if it is null.
- */
- public function init()
- {
- parent::init();
- if ($this->db === null) {
- $this->db = \Yii::$app->getComponent('db');
- }
- }
-
- /**
- * This method contains the logic to be executed when applying this migration.
- * Child classes may overwrite this method to provide actual migration logic.
- * @return boolean return a false value to indicate the migration fails
- * and should not proceed further. All other return values mean the migration succeeds.
- */
- public function up()
- {
- $transaction = $this->db->beginTransaction();
- try {
- if ($this->safeUp() === false) {
- $transaction->rollBack();
- return false;
- }
- $transaction->commit();
- } catch (\Exception $e) {
- echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
- echo $e->getTraceAsString() . "\n";
- $transaction->rollBack();
- return false;
- }
- return null;
- }
-
- /**
- * This method contains the logic to be executed when removing this migration.
- * The default implementation throws an exception indicating the migration cannot be removed.
- * Child classes may override this method if the corresponding migrations can be removed.
- * @return boolean return a false value to indicate the migration fails
- * and should not proceed further. All other return values mean the migration succeeds.
- */
- public function down()
- {
- $transaction = $this->db->beginTransaction();
- try {
- if ($this->safeDown() === false) {
- $transaction->rollBack();
- return false;
- }
- $transaction->commit();
- } catch (\Exception $e) {
- echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
- echo $e->getTraceAsString() . "\n";
- $transaction->rollBack();
- return false;
- }
- return null;
- }
-
- /**
- * This method contains the logic to be executed when applying this migration.
- * This method differs from [[up()]] in that the DB logic implemented here will
- * be enclosed within a DB transaction.
- * Child classes may implement this method instead of [[up()]] if the DB logic
- * needs to be within a transaction.
- * @return boolean return a false value to indicate the migration fails
- * and should not proceed further. All other return values mean the migration succeeds.
- */
- public function safeUp()
- {
- }
-
- /**
- * This method contains the logic to be executed when removing this migration.
- * This method differs from [[down()]] in that the DB logic implemented here will
- * be enclosed within a DB transaction.
- * Child classes may implement this method instead of [[up()]] if the DB logic
- * needs to be within a transaction.
- * @return boolean return a false value to indicate the migration fails
- * and should not proceed further. All other return values mean the migration succeeds.
- */
- public function safeDown()
- {
- }
-
- /**
- * Executes a SQL statement.
- * This method executes the specified SQL statement using [[db]].
- * @param string $sql the SQL statement to be executed
- * @param array $params input parameters (name => value) for the SQL execution.
- * See [[Command::execute()]] for more details.
- */
- public function execute($sql, $params = [])
- {
- echo " > execute SQL: $sql ...";
- $time = microtime(true);
- $this->db->createCommand($sql)->bindValues($params)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Creates and executes an INSERT SQL statement.
- * The method will properly escape the column names, and bind the values to be inserted.
- * @param string $table the table that new rows will be inserted into.
- * @param array $columns the column data (name => value) to be inserted into the table.
- */
- public function insert($table, $columns)
- {
- echo " > insert into $table ...";
- $time = microtime(true);
- $this->db->createCommand()->insert($table, $columns)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Creates and executes an batch INSERT SQL statement.
- * The method will properly escape the column names, and bind the values to be inserted.
- * @param string $table the table that new rows will be inserted into.
- * @param array $columns the column names.
- * @param array $rows the rows to be batch inserted into the table
- */
- public function batchInsert($table, $columns, $rows)
- {
- echo " > insert into $table ...";
- $time = microtime(true);
- $this->db->createCommand()->batchInsert($table, $columns, $rows)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Creates and executes an UPDATE SQL statement.
- * The method will properly escape the column names and bind the values to be updated.
- * @param string $table the table to be updated.
- * @param array $columns the column data (name => value) to be updated.
- * @param array|string $condition the conditions that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify conditions.
- * @param array $params the parameters to be bound to the query.
- */
- public function update($table, $columns, $condition = '', $params = [])
- {
- echo " > update $table ...";
- $time = microtime(true);
- $this->db->createCommand()->update($table, $columns, $condition, $params)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Creates and executes a DELETE SQL statement.
- * @param string $table the table where the data will be deleted from.
- * @param array|string $condition the conditions that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify conditions.
- * @param array $params the parameters to be bound to the query.
- */
- public function delete($table, $condition = '', $params = [])
- {
- echo " > delete from $table ...";
- $time = microtime(true);
- $this->db->createCommand()->delete($table, $condition, $params)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for creating a new DB table.
- *
- * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
- * where name stands for a column name which will be properly quoted by the method, and definition
- * stands for the column type which can contain an abstract DB type.
- *
- * The [[QueryBuilder::getColumnType()]] method will be invoked to convert any abstract type into a physical one.
- *
- * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
- * put into the generated SQL.
- *
- * @param string $table the name of the table to be created. The name will be properly quoted by the method.
- * @param array $columns the columns (name => definition) in the new table.
- * @param string $options additional SQL fragment that will be appended to the generated SQL.
- */
- public function createTable($table, $columns, $options = null)
- {
- echo " > create table $table ...";
- $time = microtime(true);
- $this->db->createCommand()->createTable($table, $columns, $options)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for renaming a DB table.
- * @param string $table the table to be renamed. The name will be properly quoted by the method.
- * @param string $newName the new table name. The name will be properly quoted by the method.
- */
- public function renameTable($table, $newName)
- {
- echo " > rename table $table to $newName ...";
- $time = microtime(true);
- $this->db->createCommand()->renameTable($table, $newName)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for dropping a DB table.
- * @param string $table the table to be dropped. The name will be properly quoted by the method.
- */
- public function dropTable($table)
- {
- echo " > drop table $table ...";
- $time = microtime(true);
- $this->db->createCommand()->dropTable($table)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for truncating a DB table.
- * @param string $table the table to be truncated. The name will be properly quoted by the method.
- */
- public function truncateTable($table)
- {
- echo " > truncate table $table ...";
- $time = microtime(true);
- $this->db->createCommand()->truncateTable($table)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for adding a new DB column.
- * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
- * @param string $column the name of the new column. The name will be properly quoted by the method.
- * @param string $type the column type. The [[QueryBuilder::getColumnType()]] method will be invoked to convert abstract column type (if any)
- * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
- * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
- */
- public function addColumn($table, $column, $type)
- {
- echo " > add column $column $type to table $table ...";
- $time = microtime(true);
- $this->db->createCommand()->addColumn($table, $column, $type)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for dropping a DB column.
- * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
- * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
- */
- public function dropColumn($table, $column)
- {
- echo " > drop column $column from table $table ...";
- $time = microtime(true);
- $this->db->createCommand()->dropColumn($table, $column)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for renaming a column.
- * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
- * @param string $name the old name of the column. The name will be properly quoted by the method.
- * @param string $newName the new name of the column. The name will be properly quoted by the method.
- */
- public function renameColumn($table, $name, $newName)
- {
- echo " > rename column $name in table $table to $newName ...";
- $time = microtime(true);
- $this->db->createCommand()->renameColumn($table, $name, $newName)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for changing the definition of a column.
- * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
- * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
- * @param string $type the new column type. The [[QueryBuilder::getColumnType()]] method will be invoked to convert abstract column type (if any)
- * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
- * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
- */
- public function alterColumn($table, $column, $type)
- {
- echo " > alter column $column in table $table to $type ...";
- $time = microtime(true);
- $this->db->createCommand()->alterColumn($table, $column, $type)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for creating a primary key.
- * The method will properly quote the table and column names.
- * @param string $name the name of the primary key constraint.
- * @param string $table the table that the primary key constraint will be added to.
- * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
- */
- public function addPrimaryKey($name, $table, $columns)
- {
- echo " > add primary key $name on $table (" . (is_array($columns) ? implode(',', $columns) : $columns).") ...";
- $time = microtime(true);
- $this->db->createCommand()->addPrimaryKey($name, $table, $columns)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for dropping a primary key.
- * @param string $name the name of the primary key constraint to be removed.
- * @param string $table the table that the primary key constraint will be removed from.
- */
- public function dropPrimaryKey($name, $table)
- {
- echo " > drop primary key $name ...";
- $time = microtime(true);
- $this->db->createCommand()->dropPrimaryKey($name, $table)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds a SQL statement for adding a foreign key constraint to an existing table.
- * The method will properly quote the table and column names.
- * @param string $name the name of the foreign key constraint.
- * @param string $table the table that the foreign key constraint will be added to.
- * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or use an array.
- * @param string $refTable the table that the foreign key references to.
- * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or use an array.
- * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- */
- public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
- {
- echo " > add foreign key $name: $table (" . implode(',', (array)$columns) . ") references $refTable (" . implode(',', (array)$refColumns) . ") ...";
- $time = microtime(true);
- $this->db->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds a SQL statement for dropping a foreign key constraint.
- * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
- */
- public function dropForeignKey($name, $table)
- {
- echo " > drop foreign key $name from table $table ...";
- $time = microtime(true);
- $this->db->createCommand()->dropForeignKey($name, $table)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for creating a new index.
- * @param string $name the name of the index. The name will be properly quoted by the method.
- * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
- * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
- * by commas or use an array. The column names will be properly quoted by the method.
- * @param boolean $unique whether to add UNIQUE constraint on the created index.
- */
- public function createIndex($name, $table, $columns, $unique = false)
- {
- echo " > create" . ($unique ? ' unique' : '') . " index $name on $table (" . implode(',', (array)$columns) . ") ...";
- $time = microtime(true);
- $this->db->createCommand()->createIndex($name, $table, $columns, $unique)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
-
- /**
- * Builds and executes a SQL statement for dropping an index.
- * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
- */
- public function dropIndex($name, $table)
- {
- echo " > drop index $name ...";
- $time = microtime(true);
- $this->db->createCommand()->dropIndex($name, $table)->execute();
- echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
- }
+ /**
+ * @var Connection the database connection that this migration should work with.
+ * If not set, it will be initialized as the 'db' application component.
+ */
+ public $db;
+
+ /**
+ * Initializes the migration.
+ * This method will set [[db]] to be the 'db' application component, if it is null.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->db === null) {
+ $this->db = \Yii::$app->getComponent('db');
+ }
+ }
+
+ /**
+ * This method contains the logic to be executed when applying this migration.
+ * Child classes may overwrite this method to provide actual migration logic.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function up()
+ {
+ $transaction = $this->db->beginTransaction();
+ try {
+ if ($this->safeUp() === false) {
+ $transaction->rollBack();
+
+ return false;
+ }
+ $transaction->commit();
+ } catch (\Exception $e) {
+ echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
+ echo $e->getTraceAsString() . "\n";
+ $transaction->rollBack();
+
+ return false;
+ }
+
+ return null;
+ }
+
+ /**
+ * This method contains the logic to be executed when removing this migration.
+ * The default implementation throws an exception indicating the migration cannot be removed.
+ * Child classes may override this method if the corresponding migrations can be removed.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function down()
+ {
+ $transaction = $this->db->beginTransaction();
+ try {
+ if ($this->safeDown() === false) {
+ $transaction->rollBack();
+
+ return false;
+ }
+ $transaction->commit();
+ } catch (\Exception $e) {
+ echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
+ echo $e->getTraceAsString() . "\n";
+ $transaction->rollBack();
+
+ return false;
+ }
+
+ return null;
+ }
+
+ /**
+ * This method contains the logic to be executed when applying this migration.
+ * This method differs from [[up()]] in that the DB logic implemented here will
+ * be enclosed within a DB transaction.
+ * Child classes may implement this method instead of [[up()]] if the DB logic
+ * needs to be within a transaction.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function safeUp()
+ {
+ }
+
+ /**
+ * This method contains the logic to be executed when removing this migration.
+ * This method differs from [[down()]] in that the DB logic implemented here will
+ * be enclosed within a DB transaction.
+ * Child classes may implement this method instead of [[up()]] if the DB logic
+ * needs to be within a transaction.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function safeDown()
+ {
+ }
+
+ /**
+ * Executes a SQL statement.
+ * This method executes the specified SQL statement using [[db]].
+ * @param string $sql the SQL statement to be executed
+ * @param array $params input parameters (name => value) for the SQL execution.
+ * See [[Command::execute()]] for more details.
+ */
+ public function execute($sql, $params = [])
+ {
+ echo " > execute SQL: $sql ...";
+ $time = microtime(true);
+ $this->db->createCommand($sql)->bindValues($params)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Creates and executes an INSERT SQL statement.
+ * The method will properly escape the column names, and bind the values to be inserted.
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column data (name => value) to be inserted into the table.
+ */
+ public function insert($table, $columns)
+ {
+ echo " > insert into $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->insert($table, $columns)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Creates and executes an batch INSERT SQL statement.
+ * The method will properly escape the column names, and bind the values to be inserted.
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names.
+ * @param array $rows the rows to be batch inserted into the table
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ echo " > insert into $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->batchInsert($table, $columns, $rows)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Creates and executes an UPDATE SQL statement.
+ * The method will properly escape the column names and bind the values to be updated.
+ * @param string $table the table to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param array|string $condition the conditions that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify conditions.
+ * @param array $params the parameters to be bound to the query.
+ */
+ public function update($table, $columns, $condition = '', $params = [])
+ {
+ echo " > update $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->update($table, $columns, $condition, $params)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Creates and executes a DELETE SQL statement.
+ * @param string $table the table where the data will be deleted from.
+ * @param array|string $condition the conditions that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify conditions.
+ * @param array $params the parameters to be bound to the query.
+ */
+ public function delete($table, $condition = '', $params = [])
+ {
+ echo " > delete from $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->delete($table, $condition, $params)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for creating a new DB table.
+ *
+ * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
+ * where name stands for a column name which will be properly quoted by the method, and definition
+ * stands for the column type which can contain an abstract DB type.
+ *
+ * The [[QueryBuilder::getColumnType()]] method will be invoked to convert any abstract type into a physical one.
+ *
+ * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
+ * put into the generated SQL.
+ *
+ * @param string $table the name of the table to be created. The name will be properly quoted by the method.
+ * @param array $columns the columns (name => definition) in the new table.
+ * @param string $options additional SQL fragment that will be appended to the generated SQL.
+ */
+ public function createTable($table, $columns, $options = null)
+ {
+ echo " > create table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->createTable($table, $columns, $options)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for renaming a DB table.
+ * @param string $table the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ */
+ public function renameTable($table, $newName)
+ {
+ echo " > rename table $table to $newName ...";
+ $time = microtime(true);
+ $this->db->createCommand()->renameTable($table, $newName)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping a DB table.
+ * @param string $table the table to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropTable($table)
+ {
+ echo " > drop table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropTable($table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ */
+ public function truncateTable($table)
+ {
+ echo " > truncate table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->truncateTable($table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for adding a new DB column.
+ * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
+ * @param string $column the name of the new column. The name will be properly quoted by the method.
+ * @param string $type the column type. The [[QueryBuilder::getColumnType()]] method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ */
+ public function addColumn($table, $column, $type)
+ {
+ echo " > add column $column $type to table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->addColumn($table, $column, $type)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropColumn($table, $column)
+ {
+ echo " > drop column $column from table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropColumn($table, $column)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $name the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ */
+ public function renameColumn($table, $name, $newName)
+ {
+ echo " > rename column $name in table $table to $newName ...";
+ $time = microtime(true);
+ $this->db->createCommand()->renameColumn($table, $name, $newName)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The [[QueryBuilder::getColumnType()]] method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ echo " > alter column $column in table $table to $type ...";
+ $time = microtime(true);
+ $this->db->createCommand()->alterColumn($table, $column, $type)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for creating a primary key.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ echo " > add primary key $name on $table (" . (is_array($columns) ? implode(',', $columns) : $columns).") ...";
+ $time = microtime(true);
+ $this->db->createCommand()->addPrimaryKey($name, $table, $columns)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping a primary key.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ echo " > drop primary key $name ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropPrimaryKey($name, $table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds a SQL statement for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or use an array.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or use an array.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ echo " > add foreign key $name: $table (" . implode(',', (array) $columns) . ") references $refTable (" . implode(',', (array) $refColumns) . ") ...";
+ $time = microtime(true);
+ $this->db->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropForeignKey($name, $table)
+ {
+ echo " > drop foreign key $name from table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropForeignKey($name, $table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for creating a new index.
+ * @param string $name the name of the index. The name will be properly quoted by the method.
+ * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+ * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas or use an array. The column names will be properly quoted by the method.
+ * @param boolean $unique whether to add UNIQUE constraint on the created index.
+ */
+ public function createIndex($name, $table, $columns, $unique = false)
+ {
+ echo " > create" . ($unique ? ' unique' : '') . " index $name on $table (" . implode(',', (array) $columns) . ") ...";
+ $time = microtime(true);
+ $this->db->createCommand()->createIndex($name, $table, $columns, $unique)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropIndex($name, $table)
+ {
+ echo " > drop index $name ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropIndex($name, $table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
}
diff --git a/framework/db/Query.php b/framework/db/Query.php
index 18f8c02b3c9..1971e783cb4 100644
--- a/framework/db/Query.php
+++ b/framework/db/Query.php
@@ -41,763 +41,784 @@
*/
class Query extends Component implements QueryInterface
{
- use QueryTrait;
-
- /**
- * @var array the columns being selected. For example, `['id', 'name']`.
- * This is used to construct the SELECT clause in a SQL statement. If not set, it means selecting all columns.
- * @see select()
- */
- public $select;
- /**
- * @var string additional option that should be appended to the 'SELECT' keyword. For example,
- * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
- */
- public $selectOption;
- /**
- * @var boolean whether to select distinct rows of data only. If this is set true,
- * the SELECT clause would be changed to SELECT DISTINCT.
- */
- public $distinct;
- /**
- * @var array the table(s) to be selected from. For example, `['tbl_user', 'tbl_post']`.
- * This is used to construct the FROM clause in a SQL statement.
- * @see from()
- */
- public $from;
- /**
- * @var array how to group the query results. For example, `['company', 'department']`.
- * This is used to construct the GROUP BY clause in a SQL statement.
- */
- public $groupBy;
- /**
- * @var array how to join with other tables. Each array element represents the specification
- * of one join which has the following structure:
- *
- * ~~~
- * [$joinType, $tableName, $joinCondition]
- * ~~~
- *
- * For example,
- *
- * ~~~
- * [
- * ['INNER JOIN', 'tbl_user', 'tbl_user.id = author_id'],
- * ['LEFT JOIN', 'tbl_team', 'tbl_team.id = team_id'],
- * ]
- * ~~~
- */
- public $join;
- /**
- * @var string|array the condition to be applied in the GROUP BY clause.
- * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
- */
- public $having;
- /**
- * @var array this is used to construct the UNION clause(s) in a SQL statement.
- * Each array element is an array of the following structure:
- *
- * - `query`: either a string or a [[Query]] object representing a query
- * - `all`: boolean, whether it should be `UNION ALL` or `UNION`
- */
- public $union;
- /**
- * @var array list of query parameter values indexed by parameter placeholders.
- * For example, `[':name' => 'Dan', ':age' => 31]`.
- */
- public $params = [];
-
-
- /**
- * Creates a DB command that can be used to execute this query.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return Command the created DB command instance.
- */
- public function createCommand($db = null)
- {
- if ($db === null) {
- $db = Yii::$app->getDb();
- }
- list ($sql, $params) = $db->getQueryBuilder()->build($this);
- return $db->createCommand($sql, $params);
- }
-
- /**
- * Starts a batch query.
- *
- * A batch query supports fetching data in batches, which can keep the memory usage under a limit.
- * This method will return a [[BatchQueryResult]] object which implements the `Iterator` interface
- * and can be traversed to retrieve the data in batches.
- *
- * For example,
- *
- * ```php
- * $query = (new Query)->from('tbl_user');
- * foreach ($query->batch() as $rows) {
- * // $rows is an array of 10 or fewer rows from tbl_user
- * }
- * ```
- *
- * @param integer $batchSize the number of records to be fetched in each batch.
- * @param Connection $db the database connection. If not set, the "db" application component will be used.
- * @return BatchQueryResult the batch query result. It implements the `Iterator` interface
- * and can be traversed to retrieve the data in batches.
- */
- public function batch($batchSize = 100, $db = null)
- {
- return Yii::createObject([
- 'class' => BatchQueryResult::className(),
- 'query' => $this,
- 'batchSize' => $batchSize,
- 'db' => $db,
- 'each' => false,
- ]);
- }
-
- /**
- * Starts a batch query and retrieves data row by row.
- * This method is similar to [[batch()]] except that in each iteration of the result,
- * only one row of data is returned. For example,
- *
- * ```php
- * $query = (new Query)->from('tbl_user');
- * foreach ($query->each() as $row) {
- * }
- * ```
- *
- * @param integer $batchSize the number of records to be fetched in each batch.
- * @param Connection $db the database connection. If not set, the "db" application component will be used.
- * @return BatchQueryResult the batch query result. It implements the `Iterator` interface
- * and can be traversed to retrieve the data in batches.
- */
- public function each($batchSize = 100, $db = null)
- {
- return Yii::createObject([
- 'class' => BatchQueryResult::className(),
- 'query' => $this,
- 'batchSize' => $batchSize,
- 'db' => $db,
- 'each' => true,
- ]);
- }
-
- /**
- * Executes the query and returns all results as an array.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null)
- {
- $rows = $this->createCommand($db)->queryAll();
- return $this->prepareResult($rows);
- }
-
- /**
- * Converts the raw query results into the format as specified by this query.
- * This method is internally used to convert the data fetched from database
- * into the format as required by this query.
- * @param array $rows the raw query result from database
- * @return array the converted query result
- */
- public function prepareResult($rows)
- {
- if ($this->indexBy === null) {
- return $rows;
- }
- $result = [];
- foreach ($rows as $row) {
- if (is_string($this->indexBy)) {
- $key = $row[$this->indexBy];
- } else {
- $key = call_user_func($this->indexBy, $row);
- }
- $result[$key] = $row;
- }
- return $result;
- }
-
- /**
- * Executes the query and returns a single row of result.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
- * results in nothing.
- */
- public function one($db = null)
- {
- return $this->createCommand($db)->queryOne();
- }
-
- /**
- * Returns the query result as a scalar value.
- * The value returned will be the first column in the first row of the query results.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return string|boolean the value of the first column in the first row of the query result.
- * False is returned if the query result is empty.
- */
- public function scalar($db = null)
- {
- return $this->createCommand($db)->queryScalar();
- }
-
- /**
- * Executes the query and returns the first column of the result.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return array the first column of the query result. An empty array is returned if the query results in nothing.
- */
- public function column($db = null)
- {
- return $this->createCommand($db)->queryColumn();
- }
-
- /**
- * Returns the number of records.
- * @param string $q the COUNT expression. Defaults to '*'.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given (or null), the `db` application component will be used.
- * @return integer number of records
- */
- public function count($q = '*', $db = null)
- {
- return $this->queryScalar("COUNT($q)", $db);
- }
-
- /**
- * Returns the sum of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the sum of the specified column values
- */
- public function sum($q, $db = null)
- {
- return $this->queryScalar("SUM($q)", $db);
- }
-
- /**
- * Returns the average of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the average of the specified column values.
- */
- public function average($q, $db = null)
- {
- return $this->queryScalar("AVG($q)", $db);
- }
-
- /**
- * Returns the minimum of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the minimum of the specified column values.
- */
- public function min($q, $db = null)
- {
- return $this->queryScalar("MIN($q)", $db);
- }
-
- /**
- * Returns the maximum of the specified column values.
- * @param string $q the column name or expression.
- * Make sure you properly quote column names in the expression.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer the maximum of the specified column values.
- */
- public function max($q, $db = null)
- {
- return $this->queryScalar("MAX($q)", $db);
- }
-
- /**
- * Returns a value indicating whether the query result contains any row of data.
- * @param Connection $db the database connection used to generate the SQL statement.
- * If this parameter is not given, the `db` application component will be used.
- * @return boolean whether the query result contains any row of data.
- */
- public function exists($db = null)
- {
- $select = $this->select;
- $this->select = [new Expression('1')];
- $command = $this->createCommand($db);
- $this->select = $select;
- return $command->queryScalar() !== false;
- }
-
- /**
- * Queries a scalar value by setting [[select]] first.
- * Restores the value of select to make this query reusable.
- * @param string|Expression $selectExpression
- * @param Connection|null $db
- * @return bool|string
- */
- private function queryScalar($selectExpression, $db)
- {
- $select = $this->select;
- $limit = $this->limit;
- $offset = $this->offset;
-
- $this->select = [$selectExpression];
- $this->limit = null;
- $this->offset = null;
- $command = $this->createCommand($db);
-
- $this->select = $select;
- $this->limit = $limit;
- $this->offset = $offset;
-
- if (empty($this->groupBy) && !$this->distinct) {
- return $command->queryScalar();
- } else {
- return (new Query)->select([$selectExpression])
- ->from(['c' => $this])
- ->createCommand($db)
- ->queryScalar();
- }
- }
-
- /**
- * Sets the SELECT part of the query.
- * @param string|array $columns the columns to be selected.
- * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
- * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- *
- * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should
- * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts.
- *
- * When the columns are specified as an array, you may also use array keys as the column aliases (if a column
- * does not need alias, do not use a string key).
- *
- * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
- * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
- * @return static the query object itself
- */
- public function select($columns, $option = null)
- {
- if (!is_array($columns)) {
- $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
- }
- $this->select = $columns;
- $this->selectOption = $option;
- return $this;
- }
-
- /**
- * Sets the value indicating whether to SELECT DISTINCT or not.
- * @param boolean $value whether to SELECT DISTINCT or not.
- * @return static the query object itself
- */
- public function distinct($value = true)
- {
- $this->distinct = $value;
- return $this;
- }
-
- /**
- * Sets the FROM part of the query.
- * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`)
- * or an array (e.g. `['tbl_user', 'tbl_profile']`) specifying one or several table names.
- * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`).
- * The method will automatically quote the table names unless it contains some parenthesis
- * (which means the table is given as a sub-query or DB expression).
- *
- * When the tables are specified as an array, you may also use the array keys as the table aliases
- * (if a table does not need alias, do not use a string key).
- *
- * Use a Query object to represent a sub-query. In this case, the corresponding array key will be used
- * as the alias for the sub-query.
- *
- * @return static the query object itself
- */
- public function from($tables)
- {
- if (!is_array($tables)) {
- $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
- }
- $this->from = $tables;
- return $this;
- }
-
- /**
- * Sets the WHERE part of the query.
- *
- * The method requires a $condition parameter, and optionally a $params parameter
- * specifying the values to be bound to the query.
- *
- * The $condition parameter should be either a string (e.g. 'id=1') or an array.
- * If the latter, it must be in one of the following two formats:
- *
- * - hash format: `['column1' => value1, 'column2' => value2, ...]`
- * - operator format: `[operator, operand1, operand2, ...]`
- *
- * A condition in hash format represents the following SQL expression in general:
- * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
- * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
- * in the generated expression. Below are some examples:
- *
- * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`.
- * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`.
- * - `['status' => null] generates `status IS NULL`.
- *
- * A condition in operator format generates the SQL expression according to the specified operator, which
- * can be one of the followings:
- *
- * - `and`: the operands should be concatenated together using `AND`. For example,
- * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
- * it will be converted into a string using the rules described here. For example,
- * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
- * The method will NOT do any quoting or escaping.
- *
- * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
- *
- * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
- * starting and ending values of the range that the column is in.
- * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
- *
- * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
- * in the generated condition.
- *
- * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
- * the range of the values that the column or DB expression should be in. For example,
- * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
- * The method will properly quote the column name and escape values in the range.
- *
- * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
- *
- * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
- * the values that the column or DB expression should be like.
- * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
- * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
- * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
- * `name LIKE '%test%' AND name LIKE '%sample%'`.
- * The method will properly quote the column name and escape special characters in the values.
- * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
- * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
- *
- * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
- * predicates when operand 2 is an array.
- *
- * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
- * in the generated condition.
- *
- * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
- * the `NOT LIKE` predicates.
- *
- * - `exists`: requires one operand which must be an instance of [[Query]] representing the sub-query.
- * It will build a `EXISTS (sub-query)` expression.
- *
- * - `not exists`: similar to the `exists` operator and builds a `NOT EXISTS (sub-query)` expression.
- *
- * @param string|array $condition the conditions that should be put in the WHERE part.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see andWhere()
- * @see orWhere()
- */
- public function where($condition, $params = [])
- {
- $this->where = $condition;
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'AND' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see where()
- * @see orWhere()
- */
- public function andWhere($condition, $params = [])
- {
- if ($this->where === null) {
- $this->where = $condition;
- } else {
- $this->where = ['and', $this->where, $condition];
- }
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'OR' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see where()
- * @see andWhere()
- */
- public function orWhere($condition, $params = [])
- {
- if ($this->where === null) {
- $this->where = $condition;
- } else {
- $this->where = ['or', $this->where, $condition];
- }
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Appends a JOIN part to the query.
- * The first parameter specifies what type of join it is.
- * @param string $type the type of join, such as INNER JOIN, LEFT JOIN.
- * @param string|array $table the table to be joined.
- *
- * Use string to represent the name of the table to be joined.
- * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
- * The method will automatically quote the table name unless it contains some parenthesis
- * (which means the table is given as a sub-query or DB expression).
- *
- * Use array to represent joining with a sub-query. The array must contain only one element.
- * The value must be a Query object representing the sub-query while the corresponding key
- * represents the alias for the sub-query.
- *
- * @param string|array $on the join condition that should appear in the ON part.
- * Please refer to [[where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return Query the query object itself
- */
- public function join($type, $table, $on = '', $params = [])
- {
- $this->join[] = [$type, $table, $on];
- return $this->addParams($params);
- }
-
- /**
- * Appends an INNER JOIN part to the query.
- * @param string|array $table the table to be joined.
- *
- * Use string to represent the name of the table to be joined.
- * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
- * The method will automatically quote the table name unless it contains some parenthesis
- * (which means the table is given as a sub-query or DB expression).
- *
- * Use array to represent joining with a sub-query. The array must contain only one element.
- * The value must be a Query object representing the sub-query while the corresponding key
- * represents the alias for the sub-query.
- *
- * @param string|array $on the join condition that should appear in the ON part.
- * Please refer to [[where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return Query the query object itself
- */
- public function innerJoin($table, $on = '', $params = [])
- {
- $this->join[] = ['INNER JOIN', $table, $on];
- return $this->addParams($params);
- }
-
- /**
- * Appends a LEFT OUTER JOIN part to the query.
- * @param string|array $table the table to be joined.
- *
- * Use string to represent the name of the table to be joined.
- * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
- * The method will automatically quote the table name unless it contains some parenthesis
- * (which means the table is given as a sub-query or DB expression).
- *
- * Use array to represent joining with a sub-query. The array must contain only one element.
- * The value must be a Query object representing the sub-query while the corresponding key
- * represents the alias for the sub-query.
- *
- * @param string|array $on the join condition that should appear in the ON part.
- * Please refer to [[where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query
- * @return Query the query object itself
- */
- public function leftJoin($table, $on = '', $params = [])
- {
- $this->join[] = ['LEFT JOIN', $table, $on];
- return $this->addParams($params);
- }
-
- /**
- * Appends a RIGHT OUTER JOIN part to the query.
- * @param string|array $table the table to be joined.
- *
- * Use string to represent the name of the table to be joined.
- * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
- * The method will automatically quote the table name unless it contains some parenthesis
- * (which means the table is given as a sub-query or DB expression).
- *
- * Use array to represent joining with a sub-query. The array must contain only one element.
- * The value must be a Query object representing the sub-query while the corresponding key
- * represents the alias for the sub-query.
- *
- * @param string|array $on the join condition that should appear in the ON part.
- * Please refer to [[where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query
- * @return Query the query object itself
- */
- public function rightJoin($table, $on = '', $params = [])
- {
- $this->join[] = ['RIGHT JOIN', $table, $on];
- return $this->addParams($params);
- }
-
- /**
- * Sets the GROUP BY part of the query.
- * @param string|array $columns the columns to be grouped by.
- * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see addGroupBy()
- */
- public function groupBy($columns)
- {
- if (!is_array($columns)) {
- $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
- }
- $this->groupBy = $columns;
- return $this;
- }
-
- /**
- * Adds additional group-by columns to the existing ones.
- * @param string|array $columns additional columns to be grouped by.
- * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see groupBy()
- */
- public function addGroupBy($columns)
- {
- if (!is_array($columns)) {
- $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
- }
- if ($this->groupBy === null) {
- $this->groupBy = $columns;
- } else {
- $this->groupBy = array_merge($this->groupBy, $columns);
- }
- return $this;
- }
-
- /**
- * Sets the HAVING part of the query.
- * @param string|array $condition the conditions to be put after HAVING.
- * Please refer to [[where()]] on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see andHaving()
- * @see orHaving()
- */
- public function having($condition, $params = [])
- {
- $this->having = $condition;
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Adds an additional HAVING condition to the existing one.
- * The new condition and the existing one will be joined using the 'AND' operator.
- * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see having()
- * @see orHaving()
- */
- public function andHaving($condition, $params = [])
- {
- if ($this->having === null) {
- $this->having = $condition;
- } else {
- $this->having = ['and', $this->having, $condition];
- }
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Adds an additional HAVING condition to the existing one.
- * The new condition and the existing one will be joined using the 'OR' operator.
- * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @param array $params the parameters (name => value) to be bound to the query.
- * @return static the query object itself
- * @see having()
- * @see andHaving()
- */
- public function orHaving($condition, $params = [])
- {
- if ($this->having === null) {
- $this->having = $condition;
- } else {
- $this->having = ['or', $this->having, $condition];
- }
- $this->addParams($params);
- return $this;
- }
-
- /**
- * Appends a SQL statement using UNION operator.
- * @param string|Query $sql the SQL statement to be appended using UNION
- * @param boolean $all TRUE if using UNION ALL and FALSE if using UNION
- * @return static the query object itself
- */
- public function union($sql, $all = false)
- {
- $this->union[] = [ 'query' => $sql, 'all' => $all ];
- return $this;
- }
-
- /**
- * Sets the parameters to be bound to the query.
- * @param array $params list of query parameter values indexed by parameter placeholders.
- * For example, `[':name' => 'Dan', ':age' => 31]`.
- * @return static the query object itself
- * @see addParams()
- */
- public function params($params)
- {
- $this->params = $params;
- return $this;
- }
-
- /**
- * Adds additional parameters to be bound to the query.
- * @param array $params list of query parameter values indexed by parameter placeholders.
- * For example, `[':name' => 'Dan', ':age' => 31]`.
- * @return static the query object itself
- * @see params()
- */
- public function addParams($params)
- {
- if (!empty($params)) {
- if (empty($this->params)) {
- $this->params = $params;
- } else {
- foreach ($params as $name => $value) {
- if (is_integer($name)) {
- $this->params[] = $value;
- } else {
- $this->params[$name] = $value;
- }
- }
- }
- }
- return $this;
- }
+ use QueryTrait;
+
+ /**
+ * @var array the columns being selected. For example, `['id', 'name']`.
+ * This is used to construct the SELECT clause in a SQL statement. If not set, it means selecting all columns.
+ * @see select()
+ */
+ public $select;
+ /**
+ * @var string additional option that should be appended to the 'SELECT' keyword. For example,
+ * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
+ */
+ public $selectOption;
+ /**
+ * @var boolean whether to select distinct rows of data only. If this is set true,
+ * the SELECT clause would be changed to SELECT DISTINCT.
+ */
+ public $distinct;
+ /**
+ * @var array the table(s) to be selected from. For example, `['tbl_user', 'tbl_post']`.
+ * This is used to construct the FROM clause in a SQL statement.
+ * @see from()
+ */
+ public $from;
+ /**
+ * @var array how to group the query results. For example, `['company', 'department']`.
+ * This is used to construct the GROUP BY clause in a SQL statement.
+ */
+ public $groupBy;
+ /**
+ * @var array how to join with other tables. Each array element represents the specification
+ * of one join which has the following structure:
+ *
+ * ~~~
+ * [$joinType, $tableName, $joinCondition]
+ * ~~~
+ *
+ * For example,
+ *
+ * ~~~
+ * [
+ * ['INNER JOIN', 'tbl_user', 'tbl_user.id = author_id'],
+ * ['LEFT JOIN', 'tbl_team', 'tbl_team.id = team_id'],
+ * ]
+ * ~~~
+ */
+ public $join;
+ /**
+ * @var string|array the condition to be applied in the GROUP BY clause.
+ * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
+ */
+ public $having;
+ /**
+ * @var array this is used to construct the UNION clause(s) in a SQL statement.
+ * Each array element is an array of the following structure:
+ *
+ * - `query`: either a string or a [[Query]] object representing a query
+ * - `all`: boolean, whether it should be `UNION ALL` or `UNION`
+ */
+ public $union;
+ /**
+ * @var array list of query parameter values indexed by parameter placeholders.
+ * For example, `[':name' => 'Dan', ':age' => 31]`.
+ */
+ public $params = [];
+
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return Command the created DB command instance.
+ */
+ public function createCommand($db = null)
+ {
+ if ($db === null) {
+ $db = Yii::$app->getDb();
+ }
+ list ($sql, $params) = $db->getQueryBuilder()->build($this);
+
+ return $db->createCommand($sql, $params);
+ }
+
+ /**
+ * Starts a batch query.
+ *
+ * A batch query supports fetching data in batches, which can keep the memory usage under a limit.
+ * This method will return a [[BatchQueryResult]] object which implements the `Iterator` interface
+ * and can be traversed to retrieve the data in batches.
+ *
+ * For example,
+ *
+ * ```php
+ * $query = (new Query)->from('tbl_user');
+ * foreach ($query->batch() as $rows) {
+ * // $rows is an array of 10 or fewer rows from tbl_user
+ * }
+ * ```
+ *
+ * @param integer $batchSize the number of records to be fetched in each batch.
+ * @param Connection $db the database connection. If not set, the "db" application component will be used.
+ * @return BatchQueryResult the batch query result. It implements the `Iterator` interface
+ * and can be traversed to retrieve the data in batches.
+ */
+ public function batch($batchSize = 100, $db = null)
+ {
+ return Yii::createObject([
+ 'class' => BatchQueryResult::className(),
+ 'query' => $this,
+ 'batchSize' => $batchSize,
+ 'db' => $db,
+ 'each' => false,
+ ]);
+ }
+
+ /**
+ * Starts a batch query and retrieves data row by row.
+ * This method is similar to [[batch()]] except that in each iteration of the result,
+ * only one row of data is returned. For example,
+ *
+ * ```php
+ * $query = (new Query)->from('tbl_user');
+ * foreach ($query->each() as $row) {
+ * }
+ * ```
+ *
+ * @param integer $batchSize the number of records to be fetched in each batch.
+ * @param Connection $db the database connection. If not set, the "db" application component will be used.
+ * @return BatchQueryResult the batch query result. It implements the `Iterator` interface
+ * and can be traversed to retrieve the data in batches.
+ */
+ public function each($batchSize = 100, $db = null)
+ {
+ return Yii::createObject([
+ 'class' => BatchQueryResult::className(),
+ 'query' => $this,
+ 'batchSize' => $batchSize,
+ 'db' => $db,
+ 'each' => true,
+ ]);
+ }
+
+ /**
+ * Executes the query and returns all results as an array.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $rows = $this->createCommand($db)->queryAll();
+
+ return $this->prepareResult($rows);
+ }
+
+ /**
+ * Converts the raw query results into the format as specified by this query.
+ * This method is internally used to convert the data fetched from database
+ * into the format as required by this query.
+ * @param array $rows the raw query result from database
+ * @return array the converted query result
+ */
+ public function prepareResult($rows)
+ {
+ if ($this->indexBy === null) {
+ return $rows;
+ }
+ $result = [];
+ foreach ($rows as $row) {
+ if (is_string($this->indexBy)) {
+ $key = $row[$this->indexBy];
+ } else {
+ $key = call_user_func($this->indexBy, $row);
+ }
+ $result[$key] = $row;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the query and returns a single row of result.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ */
+ public function one($db = null)
+ {
+ return $this->createCommand($db)->queryOne();
+ }
+
+ /**
+ * Returns the query result as a scalar value.
+ * The value returned will be the first column in the first row of the query results.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return string|boolean the value of the first column in the first row of the query result.
+ * False is returned if the query result is empty.
+ */
+ public function scalar($db = null)
+ {
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Executes the query and returns the first column of the result.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array the first column of the query result. An empty array is returned if the query results in nothing.
+ */
+ public function column($db = null)
+ {
+ return $this->createCommand($db)->queryColumn();
+ }
+
+ /**
+ * Returns the number of records.
+ * @param string $q the COUNT expression. Defaults to '*'.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given (or null), the `db` application component will be used.
+ * @return integer number of records
+ */
+ public function count($q = '*', $db = null)
+ {
+ return $this->queryScalar("COUNT($q)", $db);
+ }
+
+ /**
+ * Returns the sum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the sum of the specified column values
+ */
+ public function sum($q, $db = null)
+ {
+ return $this->queryScalar("SUM($q)", $db);
+ }
+
+ /**
+ * Returns the average of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the average of the specified column values.
+ */
+ public function average($q, $db = null)
+ {
+ return $this->queryScalar("AVG($q)", $db);
+ }
+
+ /**
+ * Returns the minimum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the minimum of the specified column values.
+ */
+ public function min($q, $db = null)
+ {
+ return $this->queryScalar("MIN($q)", $db);
+ }
+
+ /**
+ * Returns the maximum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the maximum of the specified column values.
+ */
+ public function max($q, $db = null)
+ {
+ return $this->queryScalar("MAX($q)", $db);
+ }
+
+ /**
+ * Returns a value indicating whether the query result contains any row of data.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return boolean whether the query result contains any row of data.
+ */
+ public function exists($db = null)
+ {
+ $select = $this->select;
+ $this->select = [new Expression('1')];
+ $command = $this->createCommand($db);
+ $this->select = $select;
+
+ return $command->queryScalar() !== false;
+ }
+
+ /**
+ * Queries a scalar value by setting [[select]] first.
+ * Restores the value of select to make this query reusable.
+ * @param string|Expression $selectExpression
+ * @param Connection|null $db
+ * @return bool|string
+ */
+ private function queryScalar($selectExpression, $db)
+ {
+ $select = $this->select;
+ $limit = $this->limit;
+ $offset = $this->offset;
+
+ $this->select = [$selectExpression];
+ $this->limit = null;
+ $this->offset = null;
+ $command = $this->createCommand($db);
+
+ $this->select = $select;
+ $this->limit = $limit;
+ $this->offset = $offset;
+
+ if (empty($this->groupBy) && !$this->distinct) {
+ return $command->queryScalar();
+ } else {
+ return (new Query)->select([$selectExpression])
+ ->from(['c' => $this])
+ ->createCommand($db)
+ ->queryScalar();
+ }
+ }
+
+ /**
+ * Sets the SELECT part of the query.
+ * @param string|array $columns the columns to be selected.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
+ * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ *
+ * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should
+ * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts.
+ *
+ * When the columns are specified as an array, you may also use array keys as the column aliases (if a column
+ * does not need alias, do not use a string key).
+ *
+ * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
+ * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
+ * @return static the query object itself
+ */
+ public function select($columns, $option = null)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->select = $columns;
+ $this->selectOption = $option;
+
+ return $this;
+ }
+
+ /**
+ * Sets the value indicating whether to SELECT DISTINCT or not.
+ * @param boolean $value whether to SELECT DISTINCT or not.
+ * @return static the query object itself
+ */
+ public function distinct($value = true)
+ {
+ $this->distinct = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the FROM part of the query.
+ * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`)
+ * or an array (e.g. `['tbl_user', 'tbl_profile']`) specifying one or several table names.
+ * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`).
+ * The method will automatically quote the table names unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ *
+ * When the tables are specified as an array, you may also use the array keys as the table aliases
+ * (if a table does not need alias, do not use a string key).
+ *
+ * Use a Query object to represent a sub-query. In this case, the corresponding array key will be used
+ * as the alias for the sub-query.
+ *
+ * @return static the query object itself
+ */
+ public function from($tables)
+ {
+ if (!is_array($tables)) {
+ $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->from = $tables;
+
+ return $this;
+ }
+
+ /**
+ * Sets the WHERE part of the query.
+ *
+ * The method requires a $condition parameter, and optionally a $params parameter
+ * specifying the values to be bound to the query.
+ *
+ * The $condition parameter should be either a string (e.g. 'id=1') or an array.
+ * If the latter, it must be in one of the following two formats:
+ *
+ * - hash format: `['column1' => value1, 'column2' => value2, ...]`
+ * - operator format: `[operator, operand1, operand2, ...]`
+ *
+ * A condition in hash format represents the following SQL expression in general:
+ * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
+ * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
+ * in the generated expression. Below are some examples:
+ *
+ * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`.
+ * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`.
+ * - `['status' => null] generates `status IS NULL`.
+ *
+ * A condition in operator format generates the SQL expression according to the specified operator, which
+ * can be one of the followings:
+ *
+ * - `and`: the operands should be concatenated together using `AND`. For example,
+ * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
+ * it will be converted into a string using the rules described here. For example,
+ * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
+ * The method will NOT do any quoting or escaping.
+ *
+ * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
+ *
+ * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
+ * starting and ending values of the range that the column is in.
+ * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
+ *
+ * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
+ * in the generated condition.
+ *
+ * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
+ * the range of the values that the column or DB expression should be in. For example,
+ * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
+ * The method will properly quote the column name and escape values in the range.
+ *
+ * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
+ *
+ * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
+ * the values that the column or DB expression should be like.
+ * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
+ * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
+ * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
+ * `name LIKE '%test%' AND name LIKE '%sample%'`.
+ * The method will properly quote the column name and escape special characters in the values.
+ * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
+ * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
+ *
+ * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
+ * predicates when operand 2 is an array.
+ *
+ * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
+ * in the generated condition.
+ *
+ * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
+ * the `NOT LIKE` predicates.
+ *
+ * - `exists`: requires one operand which must be an instance of [[Query]] representing the sub-query.
+ * It will build a `EXISTS (sub-query)` expression.
+ *
+ * - `not exists`: similar to the `exists` operator and builds a `NOT EXISTS (sub-query)` expression.
+ *
+ * @param string|array $condition the conditions that should be put in the WHERE part.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see andWhere()
+ * @see orWhere()
+ */
+ public function where($condition, $params = [])
+ {
+ $this->where = $condition;
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'AND' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see where()
+ * @see orWhere()
+ */
+ public function andWhere($condition, $params = [])
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = ['and', $this->where, $condition];
+ }
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'OR' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see where()
+ * @see andWhere()
+ */
+ public function orWhere($condition, $params = [])
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = ['or', $this->where, $condition];
+ }
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Appends a JOIN part to the query.
+ * The first parameter specifies what type of join it is.
+ * @param string $type the type of join, such as INNER JOIN, LEFT JOIN.
+ * @param string|array $table the table to be joined.
+ *
+ * Use string to represent the name of the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ *
+ * Use array to represent joining with a sub-query. The array must contain only one element.
+ * The value must be a Query object representing the sub-query while the corresponding key
+ * represents the alias for the sub-query.
+ *
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return Query the query object itself
+ */
+ public function join($type, $table, $on = '', $params = [])
+ {
+ $this->join[] = [$type, $table, $on];
+
+ return $this->addParams($params);
+ }
+
+ /**
+ * Appends an INNER JOIN part to the query.
+ * @param string|array $table the table to be joined.
+ *
+ * Use string to represent the name of the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ *
+ * Use array to represent joining with a sub-query. The array must contain only one element.
+ * The value must be a Query object representing the sub-query while the corresponding key
+ * represents the alias for the sub-query.
+ *
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return Query the query object itself
+ */
+ public function innerJoin($table, $on = '', $params = [])
+ {
+ $this->join[] = ['INNER JOIN', $table, $on];
+
+ return $this->addParams($params);
+ }
+
+ /**
+ * Appends a LEFT OUTER JOIN part to the query.
+ * @param string|array $table the table to be joined.
+ *
+ * Use string to represent the name of the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ *
+ * Use array to represent joining with a sub-query. The array must contain only one element.
+ * The value must be a Query object representing the sub-query while the corresponding key
+ * represents the alias for the sub-query.
+ *
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query
+ * @return Query the query object itself
+ */
+ public function leftJoin($table, $on = '', $params = [])
+ {
+ $this->join[] = ['LEFT JOIN', $table, $on];
+
+ return $this->addParams($params);
+ }
+
+ /**
+ * Appends a RIGHT OUTER JOIN part to the query.
+ * @param string|array $table the table to be joined.
+ *
+ * Use string to represent the name of the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ *
+ * Use array to represent joining with a sub-query. The array must contain only one element.
+ * The value must be a Query object representing the sub-query while the corresponding key
+ * represents the alias for the sub-query.
+ *
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query
+ * @return Query the query object itself
+ */
+ public function rightJoin($table, $on = '', $params = [])
+ {
+ $this->join[] = ['RIGHT JOIN', $table, $on];
+
+ return $this->addParams($params);
+ }
+
+ /**
+ * Sets the GROUP BY part of the query.
+ * @param string|array $columns the columns to be grouped by.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see addGroupBy()
+ */
+ public function groupBy($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->groupBy = $columns;
+
+ return $this;
+ }
+
+ /**
+ * Adds additional group-by columns to the existing ones.
+ * @param string|array $columns additional columns to be grouped by.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see groupBy()
+ */
+ public function addGroupBy($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ if ($this->groupBy === null) {
+ $this->groupBy = $columns;
+ } else {
+ $this->groupBy = array_merge($this->groupBy, $columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the HAVING part of the query.
+ * @param string|array $condition the conditions to be put after HAVING.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see andHaving()
+ * @see orHaving()
+ */
+ public function having($condition, $params = [])
+ {
+ $this->having = $condition;
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional HAVING condition to the existing one.
+ * The new condition and the existing one will be joined using the 'AND' operator.
+ * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see having()
+ * @see orHaving()
+ */
+ public function andHaving($condition, $params = [])
+ {
+ if ($this->having === null) {
+ $this->having = $condition;
+ } else {
+ $this->having = ['and', $this->having, $condition];
+ }
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional HAVING condition to the existing one.
+ * The new condition and the existing one will be joined using the 'OR' operator.
+ * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see having()
+ * @see andHaving()
+ */
+ public function orHaving($condition, $params = [])
+ {
+ if ($this->having === null) {
+ $this->having = $condition;
+ } else {
+ $this->having = ['or', $this->having, $condition];
+ }
+ $this->addParams($params);
+
+ return $this;
+ }
+
+ /**
+ * Appends a SQL statement using UNION operator.
+ * @param string|Query $sql the SQL statement to be appended using UNION
+ * @param boolean $all TRUE if using UNION ALL and FALSE if using UNION
+ * @return static the query object itself
+ */
+ public function union($sql, $all = false)
+ {
+ $this->union[] = [ 'query' => $sql, 'all' => $all ];
+
+ return $this;
+ }
+
+ /**
+ * Sets the parameters to be bound to the query.
+ * @param array $params list of query parameter values indexed by parameter placeholders.
+ * For example, `[':name' => 'Dan', ':age' => 31]`.
+ * @return static the query object itself
+ * @see addParams()
+ */
+ public function params($params)
+ {
+ $this->params = $params;
+
+ return $this;
+ }
+
+ /**
+ * Adds additional parameters to be bound to the query.
+ * @param array $params list of query parameter values indexed by parameter placeholders.
+ * For example, `[':name' => 'Dan', ':age' => 31]`.
+ * @return static the query object itself
+ * @see params()
+ */
+ public function addParams($params)
+ {
+ if (!empty($params)) {
+ if (empty($this->params)) {
+ $this->params = $params;
+ } else {
+ foreach ($params as $name => $value) {
+ if (is_integer($name)) {
+ $this->params[] = $value;
+ } else {
+ $this->params[$name] = $value;
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
}
diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php
index 124848b7573..3014418d012 100644
--- a/framework/db/QueryBuilder.php
+++ b/framework/db/QueryBuilder.php
@@ -21,1126 +21,1142 @@
*/
class QueryBuilder extends \yii\base\Object
{
- /**
- * The prefix for automatically generated query binding parameters.
- */
- const PARAM_PREFIX = ':qp';
-
- /**
- * @var Connection the database connection.
- */
- public $db;
- /**
- * @var string the separator between different fragments of a SQL statement.
- * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
- */
- public $separator = " ";
- /**
- * @var array the abstract column types mapped to physical column types.
- * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
- * Child classes should override this property to declare supported type mappings.
- */
- public $typeMap = [];
-
- /**
- * Constructor.
- * @param Connection $connection the database connection.
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($connection, $config = [])
- {
- $this->db = $connection;
- parent::__construct($config);
- }
-
- /**
- * Generates a SELECT SQL statement from a [[Query]] object.
- * @param Query $query the [[Query]] object from which the SQL statement will be generated.
- * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
- * be included in the result with the additional parameters generated during the query building process.
- * @return array the generated SQL statement (the first array element) and the corresponding
- * parameters to be bound to the SQL statement (the second array element). The parameters returned
- * include those provided in `$params`.
- */
- public function build($query, $params = [])
- {
- $params = empty($params) ? $query->params : array_merge($params, $query->params);
-
- $select = $query->select;
- $from = $query->from;
- if ($from === null && $query instanceof ActiveQuery) {
- /** @var ActiveRecord $modelClass */
- $modelClass = $query->modelClass;
- $tableName = $modelClass::tableName();
- $from = [$tableName];
- if ($select === null && !empty($query->join)) {
- $select = ["$tableName.*"];
- }
- }
-
- $clauses = [
- $this->buildSelect($select, $params, $query->distinct, $query->selectOption),
- $this->buildFrom($from, $params),
- $this->buildJoin($query->join, $params),
- $this->buildWhere($query->where, $params),
- $this->buildGroupBy($query->groupBy),
- $this->buildHaving($query->having, $params),
- $this->buildOrderBy($query->orderBy),
- $this->buildLimit($query->limit, $query->offset),
- $this->buildUnion($query->union, $params),
- ];
- return [implode($this->separator, array_filter($clauses)), $params];
- }
-
- /**
- * Creates an INSERT SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->insert('tbl_user', [
- * 'name' => 'Sam',
- * 'age' => 30,
- * ], $params);
- * ~~~
- *
- * The method will properly escape the table and column names.
- *
- * @param string $table the table that new rows will be inserted into.
- * @param array $columns the column data (name => value) to be inserted into the table.
- * @param array $params the binding parameters that will be generated by this method.
- * They should be bound to the DB command later.
- * @return string the INSERT SQL
- */
- public function insert($table, $columns, &$params)
- {
- if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
- $columnSchemas = $tableSchema->columns;
- } else {
- $columnSchemas = [];
- }
- $names = [];
- $placeholders = [];
- foreach ($columns as $name => $value) {
- $names[] = $this->db->quoteColumnName($name);
- if ($value instanceof Expression) {
- $placeholders[] = $value->expression;
- foreach ($value->params as $n => $v) {
- $params[$n] = $v;
- }
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $placeholders[] = $phName;
- $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->typecast($value) : $value;
- }
- }
-
- return 'INSERT INTO ' . $this->db->quoteTableName($table)
- . ' (' . implode(', ', $names) . ') VALUES ('
- . implode(', ', $placeholders) . ')';
- }
-
- /**
- * Generates a batch INSERT SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->batchInsert('tbl_user', ['name', 'age'], [
- * ['Tom', 30],
- * ['Jane', 20],
- * ['Linda', 25],
- * ]);
- * ~~~
- *
- * Note that the values in each row must match the corresponding column names.
- *
- * @param string $table the table that new rows will be inserted into.
- * @param array $columns the column names
- * @param array $rows the rows to be batch inserted into the table
- * @return string the batch INSERT SQL statement
- */
- public function batchInsert($table, $columns, $rows)
- {
- if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
- $columnSchemas = $tableSchema->columns;
- } else {
- $columnSchemas = [];
- }
-
- foreach ($columns as $i => $name) {
- $columns[$i] = $this->db->quoteColumnName($name);
- }
-
- $values = [];
- foreach ($rows as $row) {
- $vs = [];
- foreach ($row as $i => $value) {
- if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
- $value = $columnSchemas[$columns[$i]]->typecast($value);
- }
- $vs[] = is_string($value) ? $this->db->quoteValue($value) : ($value === null ? 'NULL' : $value);
- }
- $values[] = '(' . implode(', ', $vs) . ')';
- }
-
- return 'INSERT INTO ' . $this->db->quoteTableName($table)
- . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
- }
-
- /**
- * Creates an UPDATE SQL statement.
- * For example,
- *
- * ~~~
- * $params = [];
- * $sql = $queryBuilder->update('tbl_user', ['status' => 1], 'age > 30', $params);
- * ~~~
- *
- * The method will properly escape the table and column names.
- *
- * @param string $table the table to be updated.
- * @param array $columns the column data (name => value) to be updated.
- * @param array|string $condition the condition that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify condition.
- * @param array $params the binding parameters that will be modified by this method
- * so that they can be bound to the DB command later.
- * @return string the UPDATE SQL
- */
- public function update($table, $columns, $condition, &$params)
- {
- if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
- $columnSchemas = $tableSchema->columns;
- } else {
- $columnSchemas = [];
- }
-
- $lines = [];
- foreach ($columns as $name => $value) {
- if ($value instanceof Expression) {
- $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
- foreach ($value->params as $n => $v) {
- $params[$n] = $v;
- }
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
- $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->typecast($value) : $value;
- }
- }
-
- $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
- $where = $this->buildWhere($condition, $params);
- return $where === '' ? $sql : $sql . ' ' . $where;
- }
-
- /**
- * Creates a DELETE SQL statement.
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->delete('tbl_user', 'status = 0');
- * ~~~
- *
- * The method will properly escape the table and column names.
- *
- * @param string $table the table where the data will be deleted from.
- * @param array|string $condition the condition that will be put in the WHERE part. Please
- * refer to [[Query::where()]] on how to specify condition.
- * @param array $params the binding parameters that will be modified by this method
- * so that they can be bound to the DB command later.
- * @return string the DELETE SQL
- */
- public function delete($table, $condition, &$params)
- {
- $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
- $where = $this->buildWhere($condition, $params);
- return $where === '' ? $sql : $sql . ' ' . $where;
- }
-
- /**
- * Builds a SQL statement for creating a new DB table.
- *
- * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
- * where name stands for a column name which will be properly quoted by the method, and definition
- * stands for the column type which can contain an abstract DB type.
- * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
- *
- * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
- * inserted into the generated SQL.
- *
- * For example,
- *
- * ~~~
- * $sql = $queryBuilder->createTable('tbl_user', [
- * 'id' => 'pk',
- * 'name' => 'string',
- * 'age' => 'integer',
- * ]);
- * ~~~
- *
- * @param string $table the name of the table to be created. The name will be properly quoted by the method.
- * @param array $columns the columns (name => definition) in the new table.
- * @param string $options additional SQL fragment that will be appended to the generated SQL.
- * @return string the SQL statement for creating a new DB table.
- */
- public function createTable($table, $columns, $options = null)
- {
- $cols = [];
- foreach ($columns as $name => $type) {
- if (is_string($name)) {
- $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
- } else {
- $cols[] = "\t" . $type;
- }
- }
- $sql = "CREATE TABLE " . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
- return $options === null ? $sql : $sql . ' ' . $options;
- }
-
- /**
- * Builds a SQL statement for renaming a DB table.
- * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
- * @param string $newName the new table name. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB table.
- */
- public function renameTable($oldName, $newName)
- {
- return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
- }
-
- /**
- * Builds a SQL statement for dropping a DB table.
- * @param string $table the table to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping a DB table.
- */
- public function dropTable($table)
- {
- return "DROP TABLE " . $this->db->quoteTableName($table);
- }
-
- /**
- * Builds a SQL statement for adding a primary key constraint to an existing table.
- * @param string $name the name of the primary key constraint.
- * @param string $table the table that the primary key constraint will be added to.
- * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
- * @return string the SQL statement for adding a primary key constraint to an existing table.
- */
- public function addPrimaryKey($name, $table, $columns)
- {
- if (is_string($columns)) {
- $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
- }
-
- foreach ($columns as $i => $col) {
- $columns[$i] = $this->db->quoteColumnName($col);
- }
-
- return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
- . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
- . implode(', ', $columns). ' )';
- }
-
- /**
- * Builds a SQL statement for removing a primary key constraint to an existing table.
- * @param string $name the name of the primary key constraint to be removed.
- * @param string $table the table that the primary key constraint will be removed from.
- * @return string the SQL statement for removing a primary key constraint from an existing table. *
- */
- public function dropPrimaryKey($name, $table)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table)
- . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
- }
-
- /**
- * Builds a SQL statement for truncating a DB table.
- * @param string $table the table to be truncated. The name will be properly quoted by the method.
- * @return string the SQL statement for truncating a DB table.
- */
- public function truncateTable($table)
- {
- return "TRUNCATE TABLE " . $this->db->quoteTableName($table);
- }
-
- /**
- * Builds a SQL statement for adding a new DB column.
- * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
- * @param string $column the name of the new column. The name will be properly quoted by the method.
- * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
- * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
- * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
- * @return string the SQL statement for adding a new column.
- */
- public function addColumn($table, $column, $type)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table)
- . ' ADD ' . $this->db->quoteColumnName($column) . ' '
- . $this->getColumnType($type);
- }
-
- /**
- * Builds a SQL statement for dropping a DB column.
- * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
- * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping a DB column.
- */
- public function dropColumn($table, $column)
- {
- return "ALTER TABLE " . $this->db->quoteTableName($table)
- . " DROP COLUMN " . $this->db->quoteColumnName($column);
- }
-
- /**
- * Builds a SQL statement for renaming a column.
- * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
- * @param string $oldName the old name of the column. The name will be properly quoted by the method.
- * @param string $newName the new name of the column. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB column.
- */
- public function renameColumn($table, $oldName, $newName)
- {
- return "ALTER TABLE " . $this->db->quoteTableName($table)
- . " RENAME COLUMN " . $this->db->quoteColumnName($oldName)
- . " TO " . $this->db->quoteColumnName($newName);
- }
-
- /**
- * Builds a SQL statement for changing the definition of a column.
- * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
- * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
- * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
- * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
- * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
- * will become 'varchar(255) not null'.
- * @return string the SQL statement for changing the definition of a column.
- */
- public function alterColumn($table, $column, $type)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
- . $this->db->quoteColumnName($column) . ' '
- . $this->db->quoteColumnName($column) . ' '
- . $this->getColumnType($type);
- }
-
- /**
- * Builds a SQL statement for adding a foreign key constraint to an existing table.
- * The method will properly quote the table and column names.
- * @param string $name the name of the foreign key constraint.
- * @param string $table the table that the foreign key constraint will be added to.
- * @param string|array $columns the name of the column to that the constraint will be added on.
- * If there are multiple columns, separate them with commas or use an array to represent them.
- * @param string $refTable the table that the foreign key references to.
- * @param string|array $refColumns the name of the column that the foreign key references to.
- * If there are multiple columns, separate them with commas or use an array to represent them.
- * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- * @return string the SQL statement for adding a foreign key constraint to an existing table.
- */
- public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
- {
- $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
- . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
- . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
- . ' REFERENCES ' . $this->db->quoteTableName($refTable)
- . ' (' . $this->buildColumns($refColumns) . ')';
- if ($delete !== null) {
- $sql .= ' ON DELETE ' . $delete;
- }
- if ($update !== null) {
- $sql .= ' ON UPDATE ' . $update;
- }
- return $sql;
- }
-
- /**
- * Builds a SQL statement for dropping a foreign key constraint.
- * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping a foreign key constraint.
- */
- public function dropForeignKey($name, $table)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table)
- . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
- }
-
- /**
- * Builds a SQL statement for creating a new index.
- * @param string $name the name of the index. The name will be properly quoted by the method.
- * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
- * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
- * separate them with commas or use an array to represent them. Each column name will be properly quoted
- * by the method, unless a parenthesis is found in the name.
- * @param boolean $unique whether to add UNIQUE constraint on the created index.
- * @return string the SQL statement for creating a new index.
- */
- public function createIndex($name, $table, $columns, $unique = false)
- {
- return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
- . $this->db->quoteTableName($name) . ' ON '
- . $this->db->quoteTableName($table)
- . ' (' . $this->buildColumns($columns) . ')';
- }
-
- /**
- * Builds a SQL statement for dropping an index.
- * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping an index.
- */
- public function dropIndex($name, $table)
- {
- return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
- }
-
- /**
- * Creates a SQL statement for resetting the sequence value of a table's primary key.
- * The sequence will be reset such that the primary key of the next new row inserted
- * will have the specified value or 1.
- * @param string $table the name of the table whose primary key sequence will be reset
- * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
- * the next new row's primary key will have a value 1.
- * @return string the SQL statement for resetting sequence
- * @throws NotSupportedException if this is not supported by the underlying DBMS
- */
- public function resetSequence($table, $value = null)
- {
- throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
- }
-
- /**
- * Builds a SQL statement for enabling or disabling integrity check.
- * @param boolean $check whether to turn on or off the integrity check.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
- * @return string the SQL statement for checking integrity
- * @throws NotSupportedException if this is not supported by the underlying DBMS
- */
- public function checkIntegrity($check = true, $schema = '', $table = '')
- {
- throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
- }
-
- /**
- * Converts an abstract column type into a physical column type.
- * The conversion is done using the type map specified in [[typeMap]].
- * The following abstract column types are supported (using MySQL as an example to explain the corresponding
- * physical types):
- *
- * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
- * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
- * - `string`: string type, will be converted into "varchar(255)"
- * - `text`: a long string type, will be converted into "text"
- * - `smallint`: a small integer type, will be converted into "smallint(6)"
- * - `integer`: integer type, will be converted into "int(11)"
- * - `bigint`: a big integer type, will be converted into "bigint(20)"
- * - `boolean`: boolean type, will be converted into "tinyint(1)"
- * - `float``: float number type, will be converted into "float"
- * - `decimal`: decimal number type, will be converted into "decimal"
- * - `datetime`: datetime type, will be converted into "datetime"
- * - `timestamp`: timestamp type, will be converted into "timestamp"
- * - `time`: time type, will be converted into "time"
- * - `date`: date type, will be converted into "date"
- * - `money`: money type, will be converted into "decimal(19,4)"
- * - `binary`: binary data type, will be converted into "blob"
- *
- * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
- * the first part will be converted, and the rest of the parts will be appended to the converted result.
- * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
- *
- * For some of the abstract types you can also specify a length or precision constraint
- * by prepending it in round brackets directly to the type.
- * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
- * If the underlying DBMS does not support these kind of constraints for a type it will
- * be ignored.
- *
- * If a type cannot be found in [[typeMap]], it will be returned without any change.
- * @param string $type abstract column type
- * @return string physical column type.
- */
- public function getColumnType($type)
- {
- if (isset($this->typeMap[$type])) {
- return $this->typeMap[$type];
- } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
- if (isset($this->typeMap[$matches[1]])) {
- return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
- }
- } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
- if (isset($this->typeMap[$matches[1]])) {
- return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
- }
- }
- return $type;
- }
-
- /**
- * @param array $columns
- * @param array $params the binding parameters to be populated
- * @param boolean $distinct
- * @param string $selectOption
- * @return string the SELECT clause built from [[Query::$select]].
- */
- public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
- {
- $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
- if ($selectOption !== null) {
- $select .= ' ' . $selectOption;
- }
-
- if (empty($columns)) {
- return $select . ' *';
- }
-
- foreach ($columns as $i => $column) {
- if ($column instanceof Expression) {
- $columns[$i] = $column->expression;
- $params = array_merge($params, $column->params);
- } elseif (is_string($i)) {
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
- $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
- } elseif (strpos($column, '(') === false) {
- if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
- $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
- } else {
- $columns[$i] = $this->db->quoteColumnName($column);
- }
- }
- }
-
- return $select . ' ' . implode(', ', $columns);
- }
-
- /**
- * @param array $tables
- * @param array $params the binding parameters to be populated
- * @return string the FROM clause built from [[Query::$from]].
- */
- public function buildFrom($tables, &$params)
- {
- if (empty($tables)) {
- return '';
- }
-
- foreach ($tables as $i => $table) {
- if ($table instanceof Query) {
- list($sql, $params) = $this->build($table, $params);
- $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
- } elseif (is_string($i)) {
- if (strpos($table, '(') === false) {
- $table = $this->db->quoteTableName($table);
- }
- $tables[$i] = "$table " . $this->db->quoteTableName($i);
- } elseif (strpos($table, '(') === false) {
- if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
- $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
- } else {
- $tables[$i] = $this->db->quoteTableName($table);
- }
- }
- }
-
- return 'FROM ' . implode(', ', $tables);
- }
-
- /**
- * @param array $joins
- * @param array $params the binding parameters to be populated
- * @return string the JOIN clause built from [[Query::$join]].
- * @throws Exception if the $joins parameter is not in proper format
- */
- public function buildJoin($joins, &$params)
- {
- if (empty($joins)) {
- return '';
- }
-
- foreach ($joins as $i => $join) {
- if (!is_array($join) || !isset($join[0], $join[1])) {
- throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
- }
- // 0:join type, 1:join table, 2:on-condition (optional)
- list ($joinType, $table) = $join;
- if (is_array($table)) {
- $query = reset($table);
- if (!$query instanceof Query) {
- throw new Exception('The sub-query for join must be an instance of yii\db\Query.');
- }
- $alias = $this->db->quoteTableName(key($table));
- list ($sql, $params) = $this->build($query, $params);
- $table = "($sql) $alias";
- } elseif (strpos($table, '(') === false) {
- if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
- $table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
- } else {
- $table = $this->db->quoteTableName($table);
- }
- }
- $joins[$i] = "$joinType $table";
- if (isset($join[2])) {
- $condition = $this->buildCondition($join[2], $params);
- if ($condition !== '') {
- $joins[$i] .= ' ON ' . $condition;
- }
- }
- }
-
- return implode($this->separator, $joins);
- }
-
- /**
- * @param string|array $condition
- * @param array $params the binding parameters to be populated
- * @return string the WHERE clause built from [[Query::$where]].
- */
- public function buildWhere($condition, &$params)
- {
- $where = $this->buildCondition($condition, $params);
- return $where === '' ? '' : 'WHERE ' . $where;
- }
-
- /**
- * @param array $columns
- * @return string the GROUP BY clause
- */
- public function buildGroupBy($columns)
- {
- return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
- }
-
- /**
- * @param string|array $condition
- * @param array $params the binding parameters to be populated
- * @return string the HAVING clause built from [[Query::$having]].
- */
- public function buildHaving($condition, &$params)
- {
- $having = $this->buildCondition($condition, $params);
- return $having === '' ? '' : 'HAVING ' . $having;
- }
-
- /**
- * @param array $columns
- * @return string the ORDER BY clause built from [[Query::$orderBy]].
- */
- public function buildOrderBy($columns)
- {
- if (empty($columns)) {
- return '';
- }
- $orders = [];
- foreach ($columns as $name => $direction) {
- if ($direction instanceof Expression) {
- $orders[] = $direction->expression;
- } else {
- $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
- }
- }
-
- return 'ORDER BY ' . implode(', ', $orders);
- }
-
- /**
- * @param integer $limit
- * @param integer $offset
- * @return string the LIMIT and OFFSET clauses built from [[Query::$limit]].
- */
- public function buildLimit($limit, $offset)
- {
- $sql = '';
- if ($this->hasLimit($limit)) {
- $sql = 'LIMIT ' . $limit;
- }
- if ($this->hasOffset($offset)) {
- $sql .= ' OFFSET ' . $offset;
- }
- return ltrim($sql);
- }
-
- /**
- * Checks to see if the given limit is effective.
- * @param mixed $limit the given limit
- * @return boolean whether the limit is effective
- */
- protected function hasLimit($limit)
- {
- return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0;
- }
-
- /**
- * Checks to see if the given offset is effective.
- * @param mixed $offset the given offset
- * @return boolean whether the offset is effective
- */
- protected function hasOffset($offset)
- {
- return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0';
- }
-
- /**
- * @param array $unions
- * @param array $params the binding parameters to be populated
- * @return string the UNION clause built from [[Query::$union]].
- */
- public function buildUnion($unions, &$params)
- {
- if (empty($unions)) {
- return '';
- }
-
- $result = '';
-
- foreach ($unions as $i => $union) {
- $query = $union['query'];
- if ($query instanceof Query) {
- list($unions[$i]['query'], $params) = $this->build($query, $params);
- }
-
- $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
- }
-
- return trim($result);
- }
-
- /**
- * Processes columns and properly quote them if necessary.
- * It will join all columns into a string with comma as separators.
- * @param string|array $columns the columns to be processed
- * @return string the processing result
- */
- public function buildColumns($columns)
- {
- if (!is_array($columns)) {
- if (strpos($columns, '(') !== false) {
- return $columns;
- } else {
- $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
- }
- }
- foreach ($columns as $i => $column) {
- if ($column instanceof Expression) {
- $columns[$i] = $column->expression;
- } elseif (strpos($column, '(') === false) {
- $columns[$i] = $this->db->quoteColumnName($column);
- }
- }
- return is_array($columns) ? implode(', ', $columns) : $columns;
- }
-
-
- /**
- * Parses the condition specification and generates the corresponding SQL expression.
- * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
- * on how to specify a condition.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws InvalidParamException if the condition is in bad format
- */
- public function buildCondition($condition, &$params)
- {
- static $builders = [
- 'NOT' => 'buildNotCondition',
- 'AND' => 'buildAndCondition',
- 'OR' => 'buildAndCondition',
- 'BETWEEN' => 'buildBetweenCondition',
- 'NOT BETWEEN' => 'buildBetweenCondition',
- 'IN' => 'buildInCondition',
- 'NOT IN' => 'buildInCondition',
- 'LIKE' => 'buildLikeCondition',
- 'NOT LIKE' => 'buildLikeCondition',
- 'OR LIKE' => 'buildLikeCondition',
- 'OR NOT LIKE' => 'buildLikeCondition',
- 'EXISTS' => 'buildExistsCondition',
- 'NOT EXISTS' => 'buildExistsCondition',
- ];
-
- if (!is_array($condition)) {
- return (string)$condition;
- } elseif (empty($condition)) {
- return '';
- }
- if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
- $operator = strtoupper($condition[0]);
- if (isset($builders[$operator])) {
- $method = $builders[$operator];
- array_shift($condition);
- return $this->$method($operator, $condition, $params);
- } else {
- throw new InvalidParamException('Found unknown operator in query: ' . $operator);
- }
- } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
- return $this->buildHashCondition($condition, $params);
- }
- }
-
- /**
- * Creates a condition based on column-value pairs.
- * @param array $condition the condition specification.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- */
- public function buildHashCondition($condition, &$params)
- {
- $parts = [];
- foreach ($condition as $column => $value) {
- if (is_array($value)) { // IN condition
- $parts[] = $this->buildInCondition('IN', [$column, $value], $params);
- } else {
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
- if ($value === null) {
- $parts[] = "$column IS NULL";
- } elseif ($value instanceof Expression) {
- $parts[] = "$column=" . $value->expression;
- foreach ($value->params as $n => $v) {
- $params[$n] = $v;
- }
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $parts[] = "$column=$phName";
- $params[$phName] = $value;
- }
- }
- }
- return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
- }
-
- /**
- * Connects two or more SQL expressions with the `AND` or `OR` operator.
- * @param string $operator the operator to use for connecting the given operands
- * @param array $operands the SQL expressions to connect.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- */
- public function buildAndCondition($operator, $operands, &$params)
- {
- $parts = [];
- foreach ($operands as $operand) {
- if (is_array($operand)) {
- $operand = $this->buildCondition($operand, $params);
- }
- if ($operand !== '') {
- $parts[] = $operand;
- }
- }
- if (!empty($parts)) {
- return '(' . implode(") $operator (", $parts) . ')';
- } else {
- return '';
- }
- }
-
- /**
- * Inverts an SQL expressions with `NOT` operator.
- * @param string $operator the operator to use for connecting the given operands
- * @param array $operands the SQL expressions to connect.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws InvalidParamException if wrong number of operands have been given.
- */
- public function buildNotCondition($operator, $operands, &$params)
- {
- if (count($operands) != 1) {
- throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
- }
-
- $operand = reset($operands);
- if (is_array($operand)) {
- $operand = $this->buildCondition($operand, $params);
- }
- if ($operand === '') {
- return '';
- }
- return "$operator ($operand)";
- }
-
- /**
- * Creates an SQL expressions with the `BETWEEN` operator.
- * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
- * @param array $operands the first operand is the column name. The second and third operands
- * describe the interval that column value should be in.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws InvalidParamException if wrong number of operands have been given.
- */
- public function buildBetweenCondition($operator, $operands, &$params)
- {
- if (!isset($operands[0], $operands[1], $operands[2])) {
- throw new InvalidParamException("Operator '$operator' requires three operands.");
- }
-
- list($column, $value1, $value2) = $operands;
-
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
- $phName1 = self::PARAM_PREFIX . count($params);
- $params[$phName1] = $value1;
- $phName2 = self::PARAM_PREFIX . count($params);
- $params[$phName2] = $value2;
-
- return "$column $operator $phName1 AND $phName2";
- }
-
- /**
- * Creates an SQL expressions with the `IN` operator.
- * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
- * @param array $operands the first operand is the column name. If it is an array
- * a composite IN condition will be generated.
- * The second operand is an array of values that column value should be among.
- * If it is an empty array the generated expression will be a `false` value if
- * operator is `IN` and empty if operator is `NOT IN`.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws Exception if wrong number of operands have been given.
- */
- public function buildInCondition($operator, $operands, &$params)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new Exception("Operator '$operator' requires two operands.");
- }
-
- list($column, $values) = $operands;
-
- $values = (array)$values;
-
- if (empty($values) || $column === []) {
- return $operator === 'IN' ? '0=1' : '';
- }
-
- if (count($column) > 1) {
- return $this->buildCompositeInCondition($operator, $column, $values, $params);
- } elseif (is_array($column)) {
- $column = reset($column);
- }
- foreach ($values as $i => $value) {
- if (is_array($value)) {
- $value = isset($value[$column]) ? $value[$column] : null;
- }
- if ($value === null) {
- $values[$i] = 'NULL';
- } elseif ($value instanceof Expression) {
- $values[$i] = $value->expression;
- foreach ($value->params as $n => $v) {
- $params[$n] = $v;
- }
- } else {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = $value;
- $values[$i] = $phName;
- }
- }
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
-
- if (count($values) > 1) {
- return "$column $operator (" . implode(', ', $values) . ')';
- } else {
- $operator = $operator === 'IN' ? '=' : '<>';
- return $column . $operator . reset($values);
- }
- }
-
- protected function buildCompositeInCondition($operator, $columns, $values, &$params)
- {
- $vss = [];
- foreach ($values as $value) {
- $vs = [];
- foreach ($columns as $column) {
- if (isset($value[$column])) {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = $value[$column];
- $vs[] = $phName;
- } else {
- $vs[] = 'NULL';
- }
- }
- $vss[] = '(' . implode(', ', $vs) . ')';
- }
- foreach ($columns as $i => $column) {
- if (strpos($column, '(') === false) {
- $columns[$i] = $this->db->quoteColumnName($column);
- }
- }
- return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
- }
-
- /**
- * Creates an SQL expressions with the `LIKE` operator.
- * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
- * @param array $operands an array of two or three operands
- *
- * - The first operand is the column name.
- * - The second operand is a single value or an array of values that column value
- * should be compared with. If it is an empty array the generated expression will
- * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
- * is `NOT LIKE` or `OR NOT LIKE`.
- * - An optional third operand can also be provided to specify how to escape special characters
- * in the value(s). The operand should be an array of mappings from the special characters to their
- * escaped counterparts. If this operand is not provided, a default escape mapping will be used.
- * You may use `false` or an empty array to indicate the values are already escaped and no escape
- * should be applied. Note that when using an escape mapping (or the third operand is not provided),
- * the values will be automatically enclosed within a pair of percentage characters.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws InvalidParamException if wrong number of operands have been given.
- */
- public function buildLikeCondition($operator, $operands, &$params)
- {
- if (!isset($operands[0], $operands[1])) {
- throw new InvalidParamException("Operator '$operator' requires two operands.");
- }
-
- $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
- unset($operands[2]);
-
- list($column, $values) = $operands;
-
- $values = (array)$values;
-
- if (empty($values)) {
- return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
- }
-
- if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
- $andor = ' AND ';
- } else {
- $andor = ' OR ';
- $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
- }
-
- if (strpos($column, '(') === false) {
- $column = $this->db->quoteColumnName($column);
- }
-
- $parts = [];
- foreach ($values as $value) {
- $phName = self::PARAM_PREFIX . count($params);
- $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
- $parts[] = "$column $operator $phName";
- }
-
- return implode($andor, $parts);
- }
-
- /**
- * Creates an SQL expressions with the `EXISTS` operator.
- * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
- * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
- * @param array $params the binding parameters to be populated
- * @return string the generated SQL expression
- * @throws InvalidParamException if the operand is not a [[Query]] object.
- */
- public function buildExistsCondition($operator, $operands, &$params)
- {
- if ($operands[0] instanceof Query) {
- list($sql, $params) = $this->build($operands[0], $params);
- return "$operator ($sql)";
- } else {
- throw new InvalidParamException('Subquery for EXISTS operator must be a Query object.');
- }
- }
+ /**
+ * The prefix for automatically generated query binding parameters.
+ */
+ const PARAM_PREFIX = ':qp';
+
+ /**
+ * @var Connection the database connection.
+ */
+ public $db;
+ /**
+ * @var string the separator between different fragments of a SQL statement.
+ * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
+ */
+ public $separator = " ";
+ /**
+ * @var array the abstract column types mapped to physical column types.
+ * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
+ * Child classes should override this property to declare supported type mappings.
+ */
+ public $typeMap = [];
+
+ /**
+ * Constructor.
+ * @param Connection $connection the database connection.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($connection, $config = [])
+ {
+ $this->db = $connection;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates a SELECT SQL statement from a [[Query]] object.
+ * @param Query $query the [[Query]] object from which the SQL statement will be generated.
+ * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
+ * be included in the result with the additional parameters generated during the query building process.
+ * @return array the generated SQL statement (the first array element) and the corresponding
+ * parameters to be bound to the SQL statement (the second array element). The parameters returned
+ * include those provided in `$params`.
+ */
+ public function build($query, $params = [])
+ {
+ $params = empty($params) ? $query->params : array_merge($params, $query->params);
+
+ $select = $query->select;
+ $from = $query->from;
+ if ($from === null && $query instanceof ActiveQuery) {
+ /** @var ActiveRecord $modelClass */
+ $modelClass = $query->modelClass;
+ $tableName = $modelClass::tableName();
+ $from = [$tableName];
+ if ($select === null && !empty($query->join)) {
+ $select = ["$tableName.*"];
+ }
+ }
+
+ $clauses = [
+ $this->buildSelect($select, $params, $query->distinct, $query->selectOption),
+ $this->buildFrom($from, $params),
+ $this->buildJoin($query->join, $params),
+ $this->buildWhere($query->where, $params),
+ $this->buildGroupBy($query->groupBy),
+ $this->buildHaving($query->having, $params),
+ $this->buildOrderBy($query->orderBy),
+ $this->buildLimit($query->limit, $query->offset),
+ $this->buildUnion($query->union, $params),
+ ];
+
+ return [implode($this->separator, array_filter($clauses)), $params];
+ }
+
+ /**
+ * Creates an INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->insert('tbl_user', [
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * ], $params);
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column data (name => value) to be inserted into the table.
+ * @param array $params the binding parameters that will be generated by this method.
+ * They should be bound to the DB command later.
+ * @return string the INSERT SQL
+ */
+ public function insert($table, $columns, &$params)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = [];
+ }
+ $names = [];
+ $placeholders = [];
+ foreach ($columns as $name => $value) {
+ $names[] = $this->db->quoteColumnName($name);
+ if ($value instanceof Expression) {
+ $placeholders[] = $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $placeholders[] = $phName;
+ $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->typecast($value) : $value;
+ }
+ }
+
+ return 'INSERT INTO ' . $this->db->quoteTableName($table)
+ . ' (' . implode(', ', $names) . ') VALUES ('
+ . implode(', ', $placeholders) . ')';
+ }
+
+ /**
+ * Generates a batch INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->batchInsert('tbl_user', ['name', 'age'], [
+ * ['Tom', 30],
+ * ['Jane', 20],
+ * ['Linda', 25],
+ * ]);
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the table
+ * @return string the batch INSERT SQL statement
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = [];
+ }
+
+ foreach ($columns as $i => $name) {
+ $columns[$i] = $this->db->quoteColumnName($name);
+ }
+
+ $values = [];
+ foreach ($rows as $row) {
+ $vs = [];
+ foreach ($row as $i => $value) {
+ if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
+ $value = $columnSchemas[$columns[$i]]->typecast($value);
+ }
+ $vs[] = is_string($value) ? $this->db->quoteValue($value) : ($value === null ? 'NULL' : $value);
+ }
+ $values[] = '(' . implode(', ', $vs) . ')';
+ }
+
+ return 'INSERT INTO ' . $this->db->quoteTableName($table)
+ . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
+ }
+
+ /**
+ * Creates an UPDATE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $params = [];
+ * $sql = $queryBuilder->update('tbl_user', ['status' => 1], 'age > 30', $params);
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * @param string $table the table to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param array|string $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the DB command later.
+ * @return string the UPDATE SQL
+ */
+ public function update($table, $columns, $condition, &$params)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = [];
+ }
+
+ $lines = [];
+ foreach ($columns as $name => $value) {
+ if ($value instanceof Expression) {
+ $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
+ $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->typecast($value) : $value;
+ }
+ }
+
+ $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
+ $where = $this->buildWhere($condition, $params);
+
+ return $where === '' ? $sql : $sql . ' ' . $where;
+ }
+
+ /**
+ * Creates a DELETE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->delete('tbl_user', 'status = 0');
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * @param string $table the table where the data will be deleted from.
+ * @param array|string $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the DB command later.
+ * @return string the DELETE SQL
+ */
+ public function delete($table, $condition, &$params)
+ {
+ $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
+ $where = $this->buildWhere($condition, $params);
+
+ return $where === '' ? $sql : $sql . ' ' . $where;
+ }
+
+ /**
+ * Builds a SQL statement for creating a new DB table.
+ *
+ * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
+ * where name stands for a column name which will be properly quoted by the method, and definition
+ * stands for the column type which can contain an abstract DB type.
+ * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
+ *
+ * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
+ * inserted into the generated SQL.
+ *
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->createTable('tbl_user', [
+ * 'id' => 'pk',
+ * 'name' => 'string',
+ * 'age' => 'integer',
+ * ]);
+ * ~~~
+ *
+ * @param string $table the name of the table to be created. The name will be properly quoted by the method.
+ * @param array $columns the columns (name => definition) in the new table.
+ * @param string $options additional SQL fragment that will be appended to the generated SQL.
+ * @return string the SQL statement for creating a new DB table.
+ */
+ public function createTable($table, $columns, $options = null)
+ {
+ $cols = [];
+ foreach ($columns as $name => $type) {
+ if (is_string($name)) {
+ $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
+ } else {
+ $cols[] = "\t" . $type;
+ }
+ }
+ $sql = "CREATE TABLE " . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
+
+ return $options === null ? $sql : $sql . ' ' . $options;
+ }
+
+ /**
+ * Builds a SQL statement for renaming a DB table.
+ * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB table.
+ */
+ public function renameTable($oldName, $newName)
+ {
+ return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
+ }
+
+ /**
+ * Builds a SQL statement for dropping a DB table.
+ * @param string $table the table to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a DB table.
+ */
+ public function dropTable($table)
+ {
+ return "DROP TABLE " . $this->db->quoteTableName($table);
+ }
+
+ /**
+ * Builds a SQL statement for adding a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * @return string the SQL statement for adding a primary key constraint to an existing table.
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ if (is_string($columns)) {
+ $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
+ }
+
+ foreach ($columns as $i => $col) {
+ $columns[$i] = $this->db->quoteColumnName($col);
+ }
+
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
+ . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
+ . implode(', ', $columns). ' )';
+ }
+
+ /**
+ * Builds a SQL statement for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return string the SQL statement for removing a primary key constraint from an existing table. *
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
+ }
+
+ /**
+ * Builds a SQL statement for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ * @return string the SQL statement for truncating a DB table.
+ */
+ public function truncateTable($table)
+ {
+ return "TRUNCATE TABLE " . $this->db->quoteTableName($table);
+ }
+
+ /**
+ * Builds a SQL statement for adding a new DB column.
+ * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
+ * @param string $column the name of the new column. The name will be properly quoted by the method.
+ * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ * @return string the SQL statement for adding a new column.
+ */
+ public function addColumn($table, $column, $type)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' ADD ' . $this->db->quoteColumnName($column) . ' '
+ . $this->getColumnType($type);
+ }
+
+ /**
+ * Builds a SQL statement for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a DB column.
+ */
+ public function dropColumn($table, $column)
+ {
+ return "ALTER TABLE " . $this->db->quoteTableName($table)
+ . " DROP COLUMN " . $this->db->quoteColumnName($column);
+ }
+
+ /**
+ * Builds a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB column.
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ return "ALTER TABLE " . $this->db->quoteTableName($table)
+ . " RENAME COLUMN " . $this->db->quoteColumnName($oldName)
+ . " TO " . $this->db->quoteColumnName($newName);
+ }
+
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
+ * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
+ * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
+ * will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
+ . $this->db->quoteColumnName($column) . ' '
+ . $this->db->quoteColumnName($column) . ' '
+ . $this->getColumnType($type);
+ }
+
+ /**
+ * Builds a SQL statement for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string|array $columns the name of the column to that the constraint will be added on.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string|array $refColumns the name of the column that the foreign key references to.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @return string the SQL statement for adding a foreign key constraint to an existing table.
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
+ . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
+ . ' REFERENCES ' . $this->db->quoteTableName($refTable)
+ . ' (' . $this->buildColumns($refColumns) . ')';
+ if ($delete !== null) {
+ $sql .= ' ON DELETE ' . $delete;
+ }
+ if ($update !== null) {
+ $sql .= ' ON UPDATE ' . $update;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a foreign key constraint.
+ */
+ public function dropForeignKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
+ }
+
+ /**
+ * Builds a SQL statement for creating a new index.
+ * @param string $name the name of the index. The name will be properly quoted by the method.
+ * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+ * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
+ * separate them with commas or use an array to represent them. Each column name will be properly quoted
+ * by the method, unless a parenthesis is found in the name.
+ * @param boolean $unique whether to add UNIQUE constraint on the created index.
+ * @return string the SQL statement for creating a new index.
+ */
+ public function createIndex($name, $table, $columns, $unique = false)
+ {
+ return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
+ . $this->db->quoteTableName($name) . ' ON '
+ . $this->db->quoteTableName($table)
+ . ' (' . $this->buildColumns($columns) . ')';
+ }
+
+ /**
+ * Builds a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping an index.
+ */
+ public function dropIndex($name, $table)
+ {
+ return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
+ }
+
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $table the name of the table whose primary key sequence will be reset
+ * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function resetSequence($table, $value = null)
+ {
+ throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
+ }
+
+ /**
+ * Builds a SQL statement for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
+ * @return string the SQL statement for checking integrity
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
+ }
+
+ /**
+ * Converts an abstract column type into a physical column type.
+ * The conversion is done using the type map specified in [[typeMap]].
+ * The following abstract column types are supported (using MySQL as an example to explain the corresponding
+ * physical types):
+ *
+ * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
+ * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
+ * - `string`: string type, will be converted into "varchar(255)"
+ * - `text`: a long string type, will be converted into "text"
+ * - `smallint`: a small integer type, will be converted into "smallint(6)"
+ * - `integer`: integer type, will be converted into "int(11)"
+ * - `bigint`: a big integer type, will be converted into "bigint(20)"
+ * - `boolean`: boolean type, will be converted into "tinyint(1)"
+ * - `float``: float number type, will be converted into "float"
+ * - `decimal`: decimal number type, will be converted into "decimal"
+ * - `datetime`: datetime type, will be converted into "datetime"
+ * - `timestamp`: timestamp type, will be converted into "timestamp"
+ * - `time`: time type, will be converted into "time"
+ * - `date`: date type, will be converted into "date"
+ * - `money`: money type, will be converted into "decimal(19,4)"
+ * - `binary`: binary data type, will be converted into "blob"
+ *
+ * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
+ * the first part will be converted, and the rest of the parts will be appended to the converted result.
+ * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
+ *
+ * For some of the abstract types you can also specify a length or precision constraint
+ * by prepending it in round brackets directly to the type.
+ * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
+ * If the underlying DBMS does not support these kind of constraints for a type it will
+ * be ignored.
+ *
+ * If a type cannot be found in [[typeMap]], it will be returned without any change.
+ * @param string $type abstract column type
+ * @return string physical column type.
+ */
+ public function getColumnType($type)
+ {
+ if (isset($this->typeMap[$type])) {
+ return $this->typeMap[$type];
+ } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
+ if (isset($this->typeMap[$matches[1]])) {
+ return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
+ }
+ } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
+ if (isset($this->typeMap[$matches[1]])) {
+ return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
+ }
+ }
+
+ return $type;
+ }
+
+ /**
+ * @param array $columns
+ * @param array $params the binding parameters to be populated
+ * @param boolean $distinct
+ * @param string $selectOption
+ * @return string the SELECT clause built from [[Query::$select]].
+ */
+ public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
+ {
+ $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
+ if ($selectOption !== null) {
+ $select .= ' ' . $selectOption;
+ }
+
+ if (empty($columns)) {
+ return $select . ' *';
+ }
+
+ foreach ($columns as $i => $column) {
+ if ($column instanceof Expression) {
+ $columns[$i] = $column->expression;
+ $params = array_merge($params, $column->params);
+ } elseif (is_string($i)) {
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+ $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
+ } elseif (strpos($column, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
+ $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
+ } else {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+ }
+
+ return $select . ' ' . implode(', ', $columns);
+ }
+
+ /**
+ * @param array $tables
+ * @param array $params the binding parameters to be populated
+ * @return string the FROM clause built from [[Query::$from]].
+ */
+ public function buildFrom($tables, &$params)
+ {
+ if (empty($tables)) {
+ return '';
+ }
+
+ foreach ($tables as $i => $table) {
+ if ($table instanceof Query) {
+ list($sql, $params) = $this->build($table, $params);
+ $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
+ } elseif (is_string($i)) {
+ if (strpos($table, '(') === false) {
+ $table = $this->db->quoteTableName($table);
+ }
+ $tables[$i] = "$table " . $this->db->quoteTableName($i);
+ } elseif (strpos($table, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
+ $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
+ } else {
+ $tables[$i] = $this->db->quoteTableName($table);
+ }
+ }
+ }
+
+ return 'FROM ' . implode(', ', $tables);
+ }
+
+ /**
+ * @param array $joins
+ * @param array $params the binding parameters to be populated
+ * @return string the JOIN clause built from [[Query::$join]].
+ * @throws Exception if the $joins parameter is not in proper format
+ */
+ public function buildJoin($joins, &$params)
+ {
+ if (empty($joins)) {
+ return '';
+ }
+
+ foreach ($joins as $i => $join) {
+ if (!is_array($join) || !isset($join[0], $join[1])) {
+ throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
+ }
+ // 0:join type, 1:join table, 2:on-condition (optional)
+ list ($joinType, $table) = $join;
+ if (is_array($table)) {
+ $query = reset($table);
+ if (!$query instanceof Query) {
+ throw new Exception('The sub-query for join must be an instance of yii\db\Query.');
+ }
+ $alias = $this->db->quoteTableName(key($table));
+ list ($sql, $params) = $this->build($query, $params);
+ $table = "($sql) $alias";
+ } elseif (strpos($table, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
+ $table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
+ } else {
+ $table = $this->db->quoteTableName($table);
+ }
+ }
+ $joins[$i] = "$joinType $table";
+ if (isset($join[2])) {
+ $condition = $this->buildCondition($join[2], $params);
+ if ($condition !== '') {
+ $joins[$i] .= ' ON ' . $condition;
+ }
+ }
+ }
+
+ return implode($this->separator, $joins);
+ }
+
+ /**
+ * @param string|array $condition
+ * @param array $params the binding parameters to be populated
+ * @return string the WHERE clause built from [[Query::$where]].
+ */
+ public function buildWhere($condition, &$params)
+ {
+ $where = $this->buildCondition($condition, $params);
+
+ return $where === '' ? '' : 'WHERE ' . $where;
+ }
+
+ /**
+ * @param array $columns
+ * @return string the GROUP BY clause
+ */
+ public function buildGroupBy($columns)
+ {
+ return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
+ }
+
+ /**
+ * @param string|array $condition
+ * @param array $params the binding parameters to be populated
+ * @return string the HAVING clause built from [[Query::$having]].
+ */
+ public function buildHaving($condition, &$params)
+ {
+ $having = $this->buildCondition($condition, $params);
+
+ return $having === '' ? '' : 'HAVING ' . $having;
+ }
+
+ /**
+ * @param array $columns
+ * @return string the ORDER BY clause built from [[Query::$orderBy]].
+ */
+ public function buildOrderBy($columns)
+ {
+ if (empty($columns)) {
+ return '';
+ }
+ $orders = [];
+ foreach ($columns as $name => $direction) {
+ if ($direction instanceof Expression) {
+ $orders[] = $direction->expression;
+ } else {
+ $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
+ }
+ }
+
+ return 'ORDER BY ' . implode(', ', $orders);
+ }
+
+ /**
+ * @param integer $limit
+ * @param integer $offset
+ * @return string the LIMIT and OFFSET clauses built from [[Query::$limit]].
+ */
+ public function buildLimit($limit, $offset)
+ {
+ $sql = '';
+ if ($this->hasLimit($limit)) {
+ $sql = 'LIMIT ' . $limit;
+ }
+ if ($this->hasOffset($offset)) {
+ $sql .= ' OFFSET ' . $offset;
+ }
+
+ return ltrim($sql);
+ }
+
+ /**
+ * Checks to see if the given limit is effective.
+ * @param mixed $limit the given limit
+ * @return boolean whether the limit is effective
+ */
+ protected function hasLimit($limit)
+ {
+ return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0;
+ }
+
+ /**
+ * Checks to see if the given offset is effective.
+ * @param mixed $offset the given offset
+ * @return boolean whether the offset is effective
+ */
+ protected function hasOffset($offset)
+ {
+ return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0';
+ }
+
+ /**
+ * @param array $unions
+ * @param array $params the binding parameters to be populated
+ * @return string the UNION clause built from [[Query::$union]].
+ */
+ public function buildUnion($unions, &$params)
+ {
+ if (empty($unions)) {
+ return '';
+ }
+
+ $result = '';
+
+ foreach ($unions as $i => $union) {
+ $query = $union['query'];
+ if ($query instanceof Query) {
+ list($unions[$i]['query'], $params) = $this->build($query, $params);
+ }
+
+ $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
+ }
+
+ return trim($result);
+ }
+
+ /**
+ * Processes columns and properly quote them if necessary.
+ * It will join all columns into a string with comma as separators.
+ * @param string|array $columns the columns to be processed
+ * @return string the processing result
+ */
+ public function buildColumns($columns)
+ {
+ if (!is_array($columns)) {
+ if (strpos($columns, '(') !== false) {
+ return $columns;
+ } else {
+ $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
+ }
+ }
+ foreach ($columns as $i => $column) {
+ if ($column instanceof Expression) {
+ $columns[$i] = $column->expression;
+ } elseif (strpos($column, '(') === false) {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+
+ return is_array($columns) ? implode(', ', $columns) : $columns;
+ }
+
+ /**
+ * Parses the condition specification and generates the corresponding SQL expression.
+ * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
+ * on how to specify a condition.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws InvalidParamException if the condition is in bad format
+ */
+ public function buildCondition($condition, &$params)
+ {
+ static $builders = [
+ 'NOT' => 'buildNotCondition',
+ 'AND' => 'buildAndCondition',
+ 'OR' => 'buildAndCondition',
+ 'BETWEEN' => 'buildBetweenCondition',
+ 'NOT BETWEEN' => 'buildBetweenCondition',
+ 'IN' => 'buildInCondition',
+ 'NOT IN' => 'buildInCondition',
+ 'LIKE' => 'buildLikeCondition',
+ 'NOT LIKE' => 'buildLikeCondition',
+ 'OR LIKE' => 'buildLikeCondition',
+ 'OR NOT LIKE' => 'buildLikeCondition',
+ 'EXISTS' => 'buildExistsCondition',
+ 'NOT EXISTS' => 'buildExistsCondition',
+ ];
+
+ if (!is_array($condition)) {
+ return (string) $condition;
+ } elseif (empty($condition)) {
+ return '';
+ }
+ if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
+ $operator = strtoupper($condition[0]);
+ if (isset($builders[$operator])) {
+ $method = $builders[$operator];
+ array_shift($condition);
+
+ return $this->$method($operator, $condition, $params);
+ } else {
+ throw new InvalidParamException('Found unknown operator in query: ' . $operator);
+ }
+ } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
+
+ return $this->buildHashCondition($condition, $params);
+ }
+ }
+
+ /**
+ * Creates a condition based on column-value pairs.
+ * @param array $condition the condition specification.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ */
+ public function buildHashCondition($condition, &$params)
+ {
+ $parts = [];
+ foreach ($condition as $column => $value) {
+ if (is_array($value)) { // IN condition
+ $parts[] = $this->buildInCondition('IN', [$column, $value], $params);
+ } else {
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+ if ($value === null) {
+ $parts[] = "$column IS NULL";
+ } elseif ($value instanceof Expression) {
+ $parts[] = "$column=" . $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $parts[] = "$column=$phName";
+ $params[$phName] = $value;
+ }
+ }
+ }
+
+ return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
+ }
+
+ /**
+ * Connects two or more SQL expressions with the `AND` or `OR` operator.
+ * @param string $operator the operator to use for connecting the given operands
+ * @param array $operands the SQL expressions to connect.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ */
+ public function buildAndCondition($operator, $operands, &$params)
+ {
+ $parts = [];
+ foreach ($operands as $operand) {
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($operand, $params);
+ }
+ if ($operand !== '') {
+ $parts[] = $operand;
+ }
+ }
+ if (!empty($parts)) {
+ return '(' . implode(") $operator (", $parts) . ')';
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Inverts an SQL expressions with `NOT` operator.
+ * @param string $operator the operator to use for connecting the given operands
+ * @param array $operands the SQL expressions to connect.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws InvalidParamException if wrong number of operands have been given.
+ */
+ public function buildNotCondition($operator, $operands, &$params)
+ {
+ if (count($operands) != 1) {
+ throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
+ }
+
+ $operand = reset($operands);
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($operand, $params);
+ }
+ if ($operand === '') {
+ return '';
+ }
+
+ return "$operator ($operand)";
+ }
+
+ /**
+ * Creates an SQL expressions with the `BETWEEN` operator.
+ * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
+ * @param array $operands the first operand is the column name. The second and third operands
+ * describe the interval that column value should be in.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws InvalidParamException if wrong number of operands have been given.
+ */
+ public function buildBetweenCondition($operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1], $operands[2])) {
+ throw new InvalidParamException("Operator '$operator' requires three operands.");
+ }
+
+ list($column, $value1, $value2) = $operands;
+
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+ $phName1 = self::PARAM_PREFIX . count($params);
+ $params[$phName1] = $value1;
+ $phName2 = self::PARAM_PREFIX . count($params);
+ $params[$phName2] = $value2;
+
+ return "$column $operator $phName1 AND $phName2";
+ }
+
+ /**
+ * Creates an SQL expressions with the `IN` operator.
+ * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
+ * @param array $operands the first operand is the column name. If it is an array
+ * a composite IN condition will be generated.
+ * The second operand is an array of values that column value should be among.
+ * If it is an empty array the generated expression will be a `false` value if
+ * operator is `IN` and empty if operator is `NOT IN`.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws Exception if wrong number of operands have been given.
+ */
+ public function buildInCondition($operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new Exception("Operator '$operator' requires two operands.");
+ }
+
+ list($column, $values) = $operands;
+
+ $values = (array) $values;
+
+ if (empty($values) || $column === []) {
+ return $operator === 'IN' ? '0=1' : '';
+ }
+
+ if (count($column) > 1) {
+ return $this->buildCompositeInCondition($operator, $column, $values, $params);
+ } elseif (is_array($column)) {
+ $column = reset($column);
+ }
+ foreach ($values as $i => $value) {
+ if (is_array($value)) {
+ $value = isset($value[$column]) ? $value[$column] : null;
+ }
+ if ($value === null) {
+ $values[$i] = 'NULL';
+ } elseif ($value instanceof Expression) {
+ $values[$i] = $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $value;
+ $values[$i] = $phName;
+ }
+ }
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+
+ if (count($values) > 1) {
+ return "$column $operator (" . implode(', ', $values) . ')';
+ } else {
+ $operator = $operator === 'IN' ? '=' : '<>';
+
+ return $column . $operator . reset($values);
+ }
+ }
+
+ protected function buildCompositeInCondition($operator, $columns, $values, &$params)
+ {
+ $vss = [];
+ foreach ($values as $value) {
+ $vs = [];
+ foreach ($columns as $column) {
+ if (isset($value[$column])) {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $value[$column];
+ $vs[] = $phName;
+ } else {
+ $vs[] = 'NULL';
+ }
+ }
+ $vss[] = '(' . implode(', ', $vs) . ')';
+ }
+ foreach ($columns as $i => $column) {
+ if (strpos($column, '(') === false) {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+
+ return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
+ }
+
+ /**
+ * Creates an SQL expressions with the `LIKE` operator.
+ * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
+ * @param array $operands an array of two or three operands
+ *
+ * - The first operand is the column name.
+ * - The second operand is a single value or an array of values that column value
+ * should be compared with. If it is an empty array the generated expression will
+ * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
+ * is `NOT LIKE` or `OR NOT LIKE`.
+ * - An optional third operand can also be provided to specify how to escape special characters
+ * in the value(s). The operand should be an array of mappings from the special characters to their
+ * escaped counterparts. If this operand is not provided, a default escape mapping will be used.
+ * You may use `false` or an empty array to indicate the values are already escaped and no escape
+ * should be applied. Note that when using an escape mapping (or the third operand is not provided),
+ * the values will be automatically enclosed within a pair of percentage characters.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws InvalidParamException if wrong number of operands have been given.
+ */
+ public function buildLikeCondition($operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new InvalidParamException("Operator '$operator' requires two operands.");
+ }
+
+ $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
+ unset($operands[2]);
+
+ list($column, $values) = $operands;
+
+ $values = (array) $values;
+
+ if (empty($values)) {
+ return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
+ }
+
+ if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
+ $andor = ' AND ';
+ } else {
+ $andor = ' OR ';
+ $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
+ }
+
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+
+ $parts = [];
+ foreach ($values as $value) {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
+ $parts[] = "$column $operator $phName";
+ }
+
+ return implode($andor, $parts);
+ }
+
+ /**
+ * Creates an SQL expressions with the `EXISTS` operator.
+ * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
+ * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws InvalidParamException if the operand is not a [[Query]] object.
+ */
+ public function buildExistsCondition($operator, $operands, &$params)
+ {
+ if ($operands[0] instanceof Query) {
+ list($sql, $params) = $this->build($operands[0], $params);
+
+ return "$operator ($sql)";
+ } else {
+ throw new InvalidParamException('Subquery for EXISTS operator must be a Query object.');
+ }
+ }
}
diff --git a/framework/db/QueryInterface.php b/framework/db/QueryInterface.php
index bc0dab4e5a0..8bfac52cdae 100644
--- a/framework/db/QueryInterface.php
+++ b/framework/db/QueryInterface.php
@@ -22,184 +22,184 @@
*/
interface QueryInterface
{
- /**
- * Executes the query and returns all results as an array.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return array the query results. If the query results in nothing, an empty array will be returned.
- */
- public function all($db = null);
+ /**
+ * Executes the query and returns all results as an array.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null);
- /**
- * Executes the query and returns a single row of result.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
- * results in nothing.
- */
- public function one($db = null);
+ /**
+ * Executes the query and returns a single row of result.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ */
+ public function one($db = null);
- /**
- * Returns the number of records.
- * @param string $q the COUNT expression. Defaults to '*'.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return integer number of records
- */
- public function count($q = '*', $db = null);
+ /**
+ * Returns the number of records.
+ * @param string $q the COUNT expression. Defaults to '*'.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer number of records
+ */
+ public function count($q = '*', $db = null);
- /**
- * Returns a value indicating whether the query result contains any row of data.
- * @param Connection $db the database connection used to execute the query.
- * If this parameter is not given, the `db` application component will be used.
- * @return boolean whether the query result contains any row of data.
- */
- public function exists($db = null);
+ /**
+ * Returns a value indicating whether the query result contains any row of data.
+ * @param Connection $db the database connection used to execute the query.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return boolean whether the query result contains any row of data.
+ */
+ public function exists($db = null);
- /**
- * Sets the [[indexBy]] property.
- * @param string|callable $column the name of the column by which the query results should be indexed by.
- * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
- * row data. The signature of the callable should be:
- *
- * ~~~
- * function ($row)
- * {
- * // return the index value corresponding to $row
- * }
- * ~~~
- *
- * @return static the query object itself
- */
- public function indexBy($column);
+ /**
+ * Sets the [[indexBy]] property.
+ * @param string|callable $column the name of the column by which the query results should be indexed by.
+ * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
+ * row data. The signature of the callable should be:
+ *
+ * ~~~
+ * function ($row)
+ * {
+ * // return the index value corresponding to $row
+ * }
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function indexBy($column);
- /**
- * Sets the WHERE part of the query.
- *
- * The method requires a $condition parameter.
- *
- * The $condition parameter should be an array in one of the following two formats:
- *
- * - hash format: `['column1' => value1, 'column2' => value2, ...]`
- * - operator format: `[operator, operand1, operand2, ...]`
- *
- * A condition in hash format represents the following SQL expression in general:
- * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
- * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
- * in the generated expression. Below are some examples:
- *
- * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`.
- * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`.
- * - `['status' => null] generates `status IS NULL`.
- *
- * A condition in operator format generates the SQL expression according to the specified operator, which
- * can be one of the followings:
- *
- * - `and`: the operands should be concatenated together using `AND`. For example,
- * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
- * it will be converted into a string using the rules described here. For example,
- * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
- * The method will NOT do any quoting or escaping.
- *
- * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
- *
- * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
- * starting and ending values of the range that the column is in.
- * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
- *
- * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
- * in the generated condition.
- *
- * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
- * the range of the values that the column or DB expression should be in. For example,
- * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
- * The method will properly quote the column name and escape values in the range.
- *
- * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
- *
- * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
- * the values that the column or DB expression should be like.
- * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
- * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
- * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
- * `name LIKE '%test%' AND name LIKE '%sample%'`.
- * The method will properly quote the column name and escape special characters in the values.
- * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
- * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
- *
- * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
- * predicates when operand 2 is an array.
- *
- * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
- * in the generated condition.
- *
- * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
- * the `NOT LIKE` predicates.
- *
- * @param array $condition the conditions that should be put in the WHERE part.
- * @return static the query object itself
- * @see andWhere()
- * @see orWhere()
- */
- public function where($condition);
+ /**
+ * Sets the WHERE part of the query.
+ *
+ * The method requires a $condition parameter.
+ *
+ * The $condition parameter should be an array in one of the following two formats:
+ *
+ * - hash format: `['column1' => value1, 'column2' => value2, ...]`
+ * - operator format: `[operator, operand1, operand2, ...]`
+ *
+ * A condition in hash format represents the following SQL expression in general:
+ * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
+ * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
+ * in the generated expression. Below are some examples:
+ *
+ * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`.
+ * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`.
+ * - `['status' => null] generates `status IS NULL`.
+ *
+ * A condition in operator format generates the SQL expression according to the specified operator, which
+ * can be one of the followings:
+ *
+ * - `and`: the operands should be concatenated together using `AND`. For example,
+ * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
+ * it will be converted into a string using the rules described here. For example,
+ * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
+ * The method will NOT do any quoting or escaping.
+ *
+ * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
+ *
+ * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
+ * starting and ending values of the range that the column is in.
+ * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
+ *
+ * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
+ * in the generated condition.
+ *
+ * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
+ * the range of the values that the column or DB expression should be in. For example,
+ * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
+ * The method will properly quote the column name and escape values in the range.
+ *
+ * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
+ *
+ * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
+ * the values that the column or DB expression should be like.
+ * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
+ * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
+ * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
+ * `name LIKE '%test%' AND name LIKE '%sample%'`.
+ * The method will properly quote the column name and escape special characters in the values.
+ * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
+ * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
+ *
+ * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
+ * predicates when operand 2 is an array.
+ *
+ * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
+ * in the generated condition.
+ *
+ * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
+ * the `NOT LIKE` predicates.
+ *
+ * @param array $condition the conditions that should be put in the WHERE part.
+ * @return static the query object itself
+ * @see andWhere()
+ * @see orWhere()
+ */
+ public function where($condition);
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'AND' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @return static the query object itself
- * @see where()
- * @see orWhere()
- */
- public function andWhere($condition);
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'AND' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @return static the query object itself
+ * @see where()
+ * @see orWhere()
+ */
+ public function andWhere($condition);
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'OR' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @return static the query object itself
- * @see where()
- * @see andWhere()
- */
- public function orWhere($condition);
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'OR' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @return static the query object itself
+ * @see where()
+ * @see andWhere()
+ */
+ public function orWhere($condition);
- /**
- * Sets the ORDER BY part of the query.
- * @param string|array $columns the columns (and the directions) to be ordered by.
- * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see addOrderBy()
- */
- public function orderBy($columns);
+ /**
+ * Sets the ORDER BY part of the query.
+ * @param string|array $columns the columns (and the directions) to be ordered by.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see addOrderBy()
+ */
+ public function orderBy($columns);
- /**
- * Adds additional ORDER BY columns to the query.
- * @param string|array $columns the columns (and the directions) to be ordered by.
- * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see orderBy()
- */
- public function addOrderBy($columns);
+ /**
+ * Adds additional ORDER BY columns to the query.
+ * @param string|array $columns the columns (and the directions) to be ordered by.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see orderBy()
+ */
+ public function addOrderBy($columns);
- /**
- * Sets the LIMIT part of the query.
- * @param integer $limit the limit. Use null or negative value to disable limit.
- * @return static the query object itself
- */
- public function limit($limit);
+ /**
+ * Sets the LIMIT part of the query.
+ * @param integer $limit the limit. Use null or negative value to disable limit.
+ * @return static the query object itself
+ */
+ public function limit($limit);
- /**
- * Sets the OFFSET part of the query.
- * @param integer $offset the offset. Use null or negative value to disable offset.
- * @return static the query object itself
- */
- public function offset($offset);
+ /**
+ * Sets the OFFSET part of the query.
+ * @param integer $offset the offset. Use null or negative value to disable offset.
+ * @return static the query object itself
+ */
+ public function offset($offset);
}
diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php
index e4184eacd9f..363aa5f33f0 100644
--- a/framework/db/QueryTrait.php
+++ b/framework/db/QueryTrait.php
@@ -18,189 +18,198 @@
*/
trait QueryTrait
{
- /**
- * @var string|array query condition. This refers to the WHERE clause in a SQL statement.
- * For example, `age > 31 AND team = 1`.
- * @see where()
- */
- public $where;
- /**
- * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
- */
- public $limit;
- /**
- * @var integer zero-based offset from where the records are to be returned. If not set or
- * less than 0, it means starting from the beginning.
- */
- public $offset;
- /**
- * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
- * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
- * can be either [SORT_ASC](http://php.net/manual/en/array.constants.php#constant.sort-asc)
- * or [SORT_DESC](http://php.net/manual/en/array.constants.php#constant.sort-desc).
- * The array may also contain [[Expression]] objects. If that is the case, the expressions
- * will be converted into strings without any change.
- */
- public $orderBy;
- /**
- * @var string|callable $column the name of the column by which the query results should be indexed by.
- * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
- * row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]].
- */
- public $indexBy;
-
- /**
- * Sets the [[indexBy]] property.
- * @param string|callable $column the name of the column by which the query results should be indexed by.
- * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
- * row data. The signature of the callable should be:
- *
- * ~~~
- * function ($row)
- * {
- * // return the index value corresponding to $row
- * }
- * ~~~
- *
- * @return static the query object itself
- */
- public function indexBy($column)
- {
- $this->indexBy = $column;
- return $this;
- }
-
- /**
- * Sets the WHERE part of the query.
- *
- * See [[QueryInterface::where()]] for detailed documentation.
- *
- * @param array $condition the conditions that should be put in the WHERE part.
- * @return static the query object itself
- * @see andWhere()
- * @see orWhere()
- */
- public function where($condition)
- {
- $this->where = $condition;
- return $this;
- }
-
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'AND' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @return static the query object itself
- * @see where()
- * @see orWhere()
- */
- public function andWhere($condition)
- {
- if ($this->where === null) {
- $this->where = $condition;
- } else {
- $this->where = ['and', $this->where, $condition];
- }
- return $this;
- }
-
- /**
- * Adds an additional WHERE condition to the existing one.
- * The new condition and the existing one will be joined using the 'OR' operator.
- * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
- * on how to specify this parameter.
- * @return static the query object itself
- * @see where()
- * @see andWhere()
- */
- public function orWhere($condition)
- {
- if ($this->where === null) {
- $this->where = $condition;
- } else {
- $this->where = ['or', $this->where, $condition];
- }
- return $this;
- }
-
- /**
- * Sets the ORDER BY part of the query.
- * @param string|array $columns the columns (and the directions) to be ordered by.
- * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * Note that if your order-by is an expression containing commas, you should always use an array
- * to represent the order-by information. Otherwise, the method will not be able to correctly determine
- * the order-by columns.
- * @return static the query object itself
- * @see addOrderBy()
- */
- public function orderBy($columns)
- {
- $this->orderBy = $this->normalizeOrderBy($columns);
- return $this;
- }
-
- /**
- * Adds additional ORDER BY columns to the query.
- * @param string|array $columns the columns (and the directions) to be ordered by.
- * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
- * The method will automatically quote the column names unless a column contains some parenthesis
- * (which means the column contains a DB expression).
- * @return static the query object itself
- * @see orderBy()
- */
- public function addOrderBy($columns)
- {
- $columns = $this->normalizeOrderBy($columns);
- if ($this->orderBy === null) {
- $this->orderBy = $columns;
- } else {
- $this->orderBy = array_merge($this->orderBy, $columns);
- }
- return $this;
- }
-
- protected function normalizeOrderBy($columns)
- {
- if (is_array($columns)) {
- return $columns;
- } else {
- $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
- $result = [];
- foreach ($columns as $column) {
- if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
- $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
- } else {
- $result[$column] = SORT_ASC;
- }
- }
- return $result;
- }
- }
-
- /**
- * Sets the LIMIT part of the query.
- * @param integer $limit the limit. Use null or negative value to disable limit.
- * @return static the query object itself
- */
- public function limit($limit)
- {
- $this->limit = $limit;
- return $this;
- }
-
- /**
- * Sets the OFFSET part of the query.
- * @param integer $offset the offset. Use null or negative value to disable offset.
- * @return static the query object itself
- */
- public function offset($offset)
- {
- $this->offset = $offset;
- return $this;
- }
+ /**
+ * @var string|array query condition. This refers to the WHERE clause in a SQL statement.
+ * For example, `age > 31 AND team = 1`.
+ * @see where()
+ */
+ public $where;
+ /**
+ * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
+ */
+ public $limit;
+ /**
+ * @var integer zero-based offset from where the records are to be returned. If not set or
+ * less than 0, it means starting from the beginning.
+ */
+ public $offset;
+ /**
+ * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
+ * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
+ * can be either [SORT_ASC](http://php.net/manual/en/array.constants.php#constant.sort-asc)
+ * or [SORT_DESC](http://php.net/manual/en/array.constants.php#constant.sort-desc).
+ * The array may also contain [[Expression]] objects. If that is the case, the expressions
+ * will be converted into strings without any change.
+ */
+ public $orderBy;
+ /**
+ * @var string|callable $column the name of the column by which the query results should be indexed by.
+ * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
+ * row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]].
+ */
+ public $indexBy;
+
+ /**
+ * Sets the [[indexBy]] property.
+ * @param string|callable $column the name of the column by which the query results should be indexed by.
+ * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
+ * row data. The signature of the callable should be:
+ *
+ * ~~~
+ * function ($row)
+ * {
+ * // return the index value corresponding to $row
+ * }
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function indexBy($column)
+ {
+ $this->indexBy = $column;
+
+ return $this;
+ }
+
+ /**
+ * Sets the WHERE part of the query.
+ *
+ * See [[QueryInterface::where()]] for detailed documentation.
+ *
+ * @param array $condition the conditions that should be put in the WHERE part.
+ * @return static the query object itself
+ * @see andWhere()
+ * @see orWhere()
+ */
+ public function where($condition)
+ {
+ $this->where = $condition;
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'AND' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @return static the query object itself
+ * @see where()
+ * @see orWhere()
+ */
+ public function andWhere($condition)
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = ['and', $this->where, $condition];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'OR' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @return static the query object itself
+ * @see where()
+ * @see andWhere()
+ */
+ public function orWhere($condition)
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = ['or', $this->where, $condition];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the ORDER BY part of the query.
+ * @param string|array $columns the columns (and the directions) to be ordered by.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * Note that if your order-by is an expression containing commas, you should always use an array
+ * to represent the order-by information. Otherwise, the method will not be able to correctly determine
+ * the order-by columns.
+ * @return static the query object itself
+ * @see addOrderBy()
+ */
+ public function orderBy($columns)
+ {
+ $this->orderBy = $this->normalizeOrderBy($columns);
+
+ return $this;
+ }
+
+ /**
+ * Adds additional ORDER BY columns to the query.
+ * @param string|array $columns the columns (and the directions) to be ordered by.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see orderBy()
+ */
+ public function addOrderBy($columns)
+ {
+ $columns = $this->normalizeOrderBy($columns);
+ if ($this->orderBy === null) {
+ $this->orderBy = $columns;
+ } else {
+ $this->orderBy = array_merge($this->orderBy, $columns);
+ }
+
+ return $this;
+ }
+
+ protected function normalizeOrderBy($columns)
+ {
+ if (is_array($columns)) {
+ return $columns;
+ } else {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ $result = [];
+ foreach ($columns as $column) {
+ if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
+ $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
+ } else {
+ $result[$column] = SORT_ASC;
+ }
+ }
+
+ return $result;
+ }
+ }
+
+ /**
+ * Sets the LIMIT part of the query.
+ * @param integer $limit the limit. Use null or negative value to disable limit.
+ * @return static the query object itself
+ */
+ public function limit($limit)
+ {
+ $this->limit = $limit;
+
+ return $this;
+ }
+
+ /**
+ * Sets the OFFSET part of the query.
+ * @param integer $offset the offset. Use null or negative value to disable offset.
+ * @return static the query object itself
+ */
+ public function offset($offset)
+ {
+ $this->offset = $offset;
+
+ return $this;
+ }
}
diff --git a/framework/db/Schema.php b/framework/db/Schema.php
index 1539775f569..79625c93a94 100644
--- a/framework/db/Schema.php
+++ b/framework/db/Schema.php
@@ -31,430 +31,439 @@
*/
abstract class Schema extends Object
{
- /**
- * The followings are the supported abstract column data types.
- */
- const TYPE_PK = 'pk';
- const TYPE_BIGPK = 'bigpk';
- const TYPE_STRING = 'string';
- const TYPE_TEXT = 'text';
- const TYPE_SMALLINT = 'smallint';
- const TYPE_INTEGER = 'integer';
- const TYPE_BIGINT = 'bigint';
- const TYPE_FLOAT = 'float';
- const TYPE_DECIMAL = 'decimal';
- const TYPE_DATETIME = 'datetime';
- const TYPE_TIMESTAMP = 'timestamp';
- const TYPE_TIME = 'time';
- const TYPE_DATE = 'date';
- const TYPE_BINARY = 'binary';
- const TYPE_BOOLEAN = 'boolean';
- const TYPE_MONEY = 'money';
-
- /**
- * @var Connection the database connection
- */
- public $db;
- /**
- * @var string the default schema name used for the current session.
- */
- public $defaultSchema;
- /**
- * @var array list of ALL table names in the database
- */
- private $_tableNames = [];
- /**
- * @var array list of loaded table metadata (table name => TableSchema)
- */
- private $_tables = [];
- /**
- * @var QueryBuilder the query builder for this database
- */
- private $_builder;
-
- /**
- * Loads the metadata for the specified table.
- * @param string $name table name
- * @return TableSchema DBMS-dependent table metadata, null if the table does not exist.
- */
- abstract protected function loadTableSchema($name);
-
-
- /**
- * Obtains the metadata for the named table.
- * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
- * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
- * @return TableSchema table metadata. Null if the named table does not exist.
- */
- public function getTableSchema($name, $refresh = false)
- {
- if (isset($this->_tables[$name]) && !$refresh) {
- return $this->_tables[$name];
- }
-
- $db = $this->db;
- $realName = $this->getRawTableName($name);
-
- if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
- /** @var Cache $cache */
- $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
- if ($cache instanceof Cache) {
- $key = $this->getCacheKey($name);
- if ($refresh || ($table = $cache->get($key)) === false) {
- $table = $this->loadTableSchema($realName);
- if ($table !== null) {
- $cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency([
- 'group' => $this->getCacheGroup(),
- ]));
- }
- }
- return $this->_tables[$name] = $table;
- }
- }
- return $this->_tables[$name] = $this->loadTableSchema($realName);
- }
-
- /**
- * Returns the cache key for the specified table name.
- * @param string $name the table name
- * @return mixed the cache key
- */
- protected function getCacheKey($name)
- {
- return [
- __CLASS__,
- $this->db->dsn,
- $this->db->username,
- $name,
- ];
- }
-
- /**
- * Returns the cache group name.
- * This allows [[refresh()]] to invalidate all cached table schemas.
- * @return string the cache group name
- */
- protected function getCacheGroup()
- {
- return md5(serialize([
- __CLASS__,
- $this->db->dsn,
- $this->db->username,
- ]));
- }
-
- /**
- * Returns the metadata for all tables in the database.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
- * @param boolean $refresh whether to fetch the latest available table schemas. If this is false,
- * cached data may be returned if available.
- * @return TableSchema[] the metadata for all tables in the database.
- * Each array element is an instance of [[TableSchema]] or its child class.
- */
- public function getTableSchemas($schema = '', $refresh = false)
- {
- $tables = [];
- foreach ($this->getTableNames($schema, $refresh) as $name) {
- if ($schema !== '') {
- $name = $schema . '.' . $name;
- }
- if (($table = $this->getTableSchema($name, $refresh)) !== null) {
- $tables[] = $table;
- }
- }
- return $tables;
- }
-
- /**
- * Returns all table names in the database.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
- * If not empty, the returned table names will be prefixed with the schema name.
- * @param boolean $refresh whether to fetch the latest available table names. If this is false,
- * table names fetched previously (if available) will be returned.
- * @return string[] all table names in the database.
- */
- public function getTableNames($schema = '', $refresh = false)
- {
- if (!isset($this->_tableNames[$schema]) || $refresh) {
- $this->_tableNames[$schema] = $this->findTableNames($schema);
- }
- return $this->_tableNames[$schema];
- }
-
- /**
- * @return QueryBuilder the query builder for this connection.
- */
- public function getQueryBuilder()
- {
- if ($this->_builder === null) {
- $this->_builder = $this->createQueryBuilder();
- }
- return $this->_builder;
- }
-
- /**
- * Determines the PDO type for the given PHP data value.
- * @param mixed $data the data whose PDO type is to be determined
- * @return integer the PDO type
- * @see http://www.php.net/manual/en/pdo.constants.php
- */
- public function getPdoType($data)
- {
- static $typeMap = [
- // php type => PDO type
- 'boolean' => \PDO::PARAM_BOOL,
- 'integer' => \PDO::PARAM_INT,
- 'string' => \PDO::PARAM_STR,
- 'resource' => \PDO::PARAM_LOB,
- 'NULL' => \PDO::PARAM_NULL,
- ];
- $type = gettype($data);
- return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
- }
-
- /**
- * Refreshes the schema.
- * This method cleans up all cached table schemas so that they can be re-created later
- * to reflect the database schema change.
- */
- public function refresh()
- {
- /** @var Cache $cache */
- $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
- if ($this->db->enableSchemaCache && $cache instanceof Cache) {
- GroupDependency::invalidate($cache, $this->getCacheGroup());
- }
- $this->_tableNames = [];
- $this->_tables = [];
- }
-
- /**
- * Creates a query builder for the database.
- * This method may be overridden by child classes to create a DBMS-specific query builder.
- * @return QueryBuilder query builder instance
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
-
- /**
- * Returns all table names in the database.
- * This method should be overridden by child classes in order to support this feature
- * because the default implementation simply throws an exception.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @return array all table names in the database. The names have NO schema name prefix.
- * @throws NotSupportedException if this method is called
- */
- protected function findTableNames($schema = '')
- {
- throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
- }
-
- /**
- * Returns all unique indexes for the given table.
- * Each array element is of the following structure:
- *
- * ~~~
- * [
- * 'IndexName1' => ['col1' [, ...]],
- * 'IndexName2' => ['col2' [, ...]],
- * ]
- * ~~~
- *
- * This method should be overridden by child classes in order to support this feature
- * because the default implementation simply throws an exception
- * @param TableSchema $table the table metadata
- * @return array all unique indexes for the given table.
- * @throws NotSupportedException if this method is called
- */
- public function findUniqueIndexes($table)
- {
- throw new NotSupportedException(get_class($this) . ' does not support getting unique indexes information.');
- }
-
- /**
- * Returns the ID of the last inserted row or sequence value.
- * @param string $sequenceName name of the sequence object (required by some DBMS)
- * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
- * @throws InvalidCallException if the DB connection is not active
- * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
- */
- public function getLastInsertID($sequenceName = '')
- {
- if ($this->db->isActive) {
- return $this->db->pdo->lastInsertId($sequenceName);
- } else {
- throw new InvalidCallException('DB Connection is not active.');
- }
- }
-
- /**
- * @return boolean whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
- */
- public function supportsSavepoint()
- {
- return $this->db->enableSavepoint;
- }
-
- /**
- * Creates a new savepoint.
- * @param string $name the savepoint name
- */
- public function createSavepoint($name)
- {
- $this->db->createCommand("SAVEPOINT $name")->execute();
- }
-
- /**
- * Releases an existing savepoint.
- * @param string $name the savepoint name
- */
- public function releaseSavepoint($name)
- {
- $this->db->createCommand("RELEASE SAVEPOINT $name")->execute();
- }
-
- /**
- * Rolls back to a previously created savepoint.
- * @param string $name the savepoint name
- */
- public function rollBackSavepoint($name)
- {
- $this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute();
- }
-
- /**
- * Quotes a string value for use in a query.
- * Note that if the parameter is not a string, it will be returned without change.
- * @param string $str string to be quoted
- * @return string the properly quoted string
- * @see http://www.php.net/manual/en/function.PDO-quote.php
- */
- public function quoteValue($str)
- {
- if (!is_string($str)) {
- return $str;
- }
-
- $this->db->open();
- if (($value = $this->db->pdo->quote($str)) !== false) {
- return $value;
- } else { // the driver doesn't support quote (e.g. oci)
- return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
- }
- }
-
- /**
- * Quotes a table name for use in a query.
- * If the table name contains schema prefix, the prefix will also be properly quoted.
- * If the table name is already quoted or contains '(' or '{{',
- * then this method will do nothing.
- * @param string $name table name
- * @return string the properly quoted table name
- * @see quoteSimpleTableName()
- */
- public function quoteTableName($name)
- {
- if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
- return $name;
- }
- if (strpos($name, '.') === false) {
- return $this->quoteSimpleTableName($name);
- }
- $parts = explode('.', $name);
- foreach ($parts as $i => $part) {
- $parts[$i] = $this->quoteSimpleTableName($part);
- }
- return implode('.', $parts);
-
- }
-
- /**
- * Quotes a column name for use in a query.
- * If the column name contains prefix, the prefix will also be properly quoted.
- * If the column name is already quoted or contains '(', '[[' or '{{',
- * then this method will do nothing.
- * @param string $name column name
- * @return string the properly quoted column name
- * @see quoteSimpleColumnName()
- */
- public function quoteColumnName($name)
- {
- if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
- return $name;
- }
- if (($pos = strrpos($name, '.')) !== false) {
- $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
- $name = substr($name, $pos + 1);
- } else {
- $prefix = '';
- }
- return $prefix . $this->quoteSimpleColumnName($name);
- }
-
- /**
- * Quotes a simple table name for use in a query.
- * A simple table name should contain the table name only without any schema prefix.
- * If the table name is already quoted, this method will do nothing.
- * @param string $name table name
- * @return string the properly quoted table name
- */
- public function quoteSimpleTableName($name)
- {
- return strpos($name, "'") !== false ? $name : "'" . $name . "'";
- }
-
- /**
- * Quotes a simple column name for use in a query.
- * A simple column name should contain the column name only without any prefix.
- * If the column name is already quoted or is the asterisk character '*', this method will do nothing.
- * @param string $name column name
- * @return string the properly quoted column name
- */
- public function quoteSimpleColumnName($name)
- {
- return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
- }
-
- /**
- * Returns the actual name of a given table name.
- * This method will strip off curly brackets from the given table name
- * and replace the percentage character '%' with [[Connection::tablePrefix]].
- * @param string $name the table name to be converted
- * @return string the real name of the given table name
- */
- public function getRawTableName($name)
- {
- if (strpos($name, '{{') !== false) {
- $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
- return str_replace('%', $this->db->tablePrefix, $name);
- } else {
- return $name;
- }
- }
-
- /**
- * Extracts the PHP type from abstract DB type.
- * @param ColumnSchema $column the column schema information
- * @return string PHP type name
- */
- protected function getColumnPhpType($column)
- {
- static $typeMap = [ // abstract type => php type
- 'smallint' => 'integer',
- 'integer' => 'integer',
- 'boolean' => 'boolean',
- 'float' => 'double',
- ];
- if (isset($typeMap[$column->type])) {
- if ($column->type === 'integer') {
- return $column->unsigned ? 'string' : 'integer';
- } else {
- return $typeMap[$column->type];
- }
- } else {
- return 'string';
- }
- }
+ /**
+ * The followings are the supported abstract column data types.
+ */
+ const TYPE_PK = 'pk';
+ const TYPE_BIGPK = 'bigpk';
+ const TYPE_STRING = 'string';
+ const TYPE_TEXT = 'text';
+ const TYPE_SMALLINT = 'smallint';
+ const TYPE_INTEGER = 'integer';
+ const TYPE_BIGINT = 'bigint';
+ const TYPE_FLOAT = 'float';
+ const TYPE_DECIMAL = 'decimal';
+ const TYPE_DATETIME = 'datetime';
+ const TYPE_TIMESTAMP = 'timestamp';
+ const TYPE_TIME = 'time';
+ const TYPE_DATE = 'date';
+ const TYPE_BINARY = 'binary';
+ const TYPE_BOOLEAN = 'boolean';
+ const TYPE_MONEY = 'money';
+
+ /**
+ * @var Connection the database connection
+ */
+ public $db;
+ /**
+ * @var string the default schema name used for the current session.
+ */
+ public $defaultSchema;
+ /**
+ * @var array list of ALL table names in the database
+ */
+ private $_tableNames = [];
+ /**
+ * @var array list of loaded table metadata (table name => TableSchema)
+ */
+ private $_tables = [];
+ /**
+ * @var QueryBuilder the query builder for this database
+ */
+ private $_builder;
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema DBMS-dependent table metadata, null if the table does not exist.
+ */
+ abstract protected function loadTableSchema($name);
+
+ /**
+ * Obtains the metadata for the named table.
+ * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
+ * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
+ * @return TableSchema table metadata. Null if the named table does not exist.
+ */
+ public function getTableSchema($name, $refresh = false)
+ {
+ if (isset($this->_tables[$name]) && !$refresh) {
+ return $this->_tables[$name];
+ }
+
+ $db = $this->db;
+ $realName = $this->getRawTableName($name);
+
+ if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
+ /** @var Cache $cache */
+ $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
+ if ($cache instanceof Cache) {
+ $key = $this->getCacheKey($name);
+ if ($refresh || ($table = $cache->get($key)) === false) {
+ $table = $this->loadTableSchema($realName);
+ if ($table !== null) {
+ $cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency([
+ 'group' => $this->getCacheGroup(),
+ ]));
+ }
+ }
+
+ return $this->_tables[$name] = $table;
+ }
+ }
+
+ return $this->_tables[$name] = $this->loadTableSchema($realName);
+ }
+
+ /**
+ * Returns the cache key for the specified table name.
+ * @param string $name the table name
+ * @return mixed the cache key
+ */
+ protected function getCacheKey($name)
+ {
+ return [
+ __CLASS__,
+ $this->db->dsn,
+ $this->db->username,
+ $name,
+ ];
+ }
+
+ /**
+ * Returns the cache group name.
+ * This allows [[refresh()]] to invalidate all cached table schemas.
+ * @return string the cache group name
+ */
+ protected function getCacheGroup()
+ {
+ return md5(serialize([
+ __CLASS__,
+ $this->db->dsn,
+ $this->db->username,
+ ]));
+ }
+
+ /**
+ * Returns the metadata for all tables in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
+ * @param boolean $refresh whether to fetch the latest available table schemas. If this is false,
+ * cached data may be returned if available.
+ * @return TableSchema[] the metadata for all tables in the database.
+ * Each array element is an instance of [[TableSchema]] or its child class.
+ */
+ public function getTableSchemas($schema = '', $refresh = false)
+ {
+ $tables = [];
+ foreach ($this->getTableNames($schema, $refresh) as $name) {
+ if ($schema !== '') {
+ $name = $schema . '.' . $name;
+ }
+ if (($table = $this->getTableSchema($name, $refresh)) !== null) {
+ $tables[] = $table;
+ }
+ }
+
+ return $tables;
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
+ * If not empty, the returned table names will be prefixed with the schema name.
+ * @param boolean $refresh whether to fetch the latest available table names. If this is false,
+ * table names fetched previously (if available) will be returned.
+ * @return string[] all table names in the database.
+ */
+ public function getTableNames($schema = '', $refresh = false)
+ {
+ if (!isset($this->_tableNames[$schema]) || $refresh) {
+ $this->_tableNames[$schema] = $this->findTableNames($schema);
+ }
+
+ return $this->_tableNames[$schema];
+ }
+
+ /**
+ * @return QueryBuilder the query builder for this connection.
+ */
+ public function getQueryBuilder()
+ {
+ if ($this->_builder === null) {
+ $this->_builder = $this->createQueryBuilder();
+ }
+
+ return $this->_builder;
+ }
+
+ /**
+ * Determines the PDO type for the given PHP data value.
+ * @param mixed $data the data whose PDO type is to be determined
+ * @return integer the PDO type
+ * @see http://www.php.net/manual/en/pdo.constants.php
+ */
+ public function getPdoType($data)
+ {
+ static $typeMap = [
+ // php type => PDO type
+ 'boolean' => \PDO::PARAM_BOOL,
+ 'integer' => \PDO::PARAM_INT,
+ 'string' => \PDO::PARAM_STR,
+ 'resource' => \PDO::PARAM_LOB,
+ 'NULL' => \PDO::PARAM_NULL,
+ ];
+ $type = gettype($data);
+
+ return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
+ }
+
+ /**
+ * Refreshes the schema.
+ * This method cleans up all cached table schemas so that they can be re-created later
+ * to reflect the database schema change.
+ */
+ public function refresh()
+ {
+ /** @var Cache $cache */
+ $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
+ if ($this->db->enableSchemaCache && $cache instanceof Cache) {
+ GroupDependency::invalidate($cache, $this->getCacheGroup());
+ }
+ $this->_tableNames = [];
+ $this->_tables = [];
+ }
+
+ /**
+ * Creates a query builder for the database.
+ * This method may be overridden by child classes to create a DBMS-specific query builder.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Returns all table names in the database.
+ * This method should be overridden by child classes in order to support this feature
+ * because the default implementation simply throws an exception.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ * @throws NotSupportedException if this method is called
+ */
+ protected function findTableNames($schema = '')
+ {
+ throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
+ }
+
+ /**
+ * Returns all unique indexes for the given table.
+ * Each array element is of the following structure:
+ *
+ * ~~~
+ * [
+ * 'IndexName1' => ['col1' [, ...]],
+ * 'IndexName2' => ['col2' [, ...]],
+ * ]
+ * ~~~
+ *
+ * This method should be overridden by child classes in order to support this feature
+ * because the default implementation simply throws an exception
+ * @param TableSchema $table the table metadata
+ * @return array all unique indexes for the given table.
+ * @throws NotSupportedException if this method is called
+ */
+ public function findUniqueIndexes($table)
+ {
+ throw new NotSupportedException(get_class($this) . ' does not support getting unique indexes information.');
+ }
+
+ /**
+ * Returns the ID of the last inserted row or sequence value.
+ * @param string $sequenceName name of the sequence object (required by some DBMS)
+ * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
+ * @throws InvalidCallException if the DB connection is not active
+ * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
+ */
+ public function getLastInsertID($sequenceName = '')
+ {
+ if ($this->db->isActive) {
+ return $this->db->pdo->lastInsertId($sequenceName);
+ } else {
+ throw new InvalidCallException('DB Connection is not active.');
+ }
+ }
+
+ /**
+ * @return boolean whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
+ */
+ public function supportsSavepoint()
+ {
+ return $this->db->enableSavepoint;
+ }
+
+ /**
+ * Creates a new savepoint.
+ * @param string $name the savepoint name
+ */
+ public function createSavepoint($name)
+ {
+ $this->db->createCommand("SAVEPOINT $name")->execute();
+ }
+
+ /**
+ * Releases an existing savepoint.
+ * @param string $name the savepoint name
+ */
+ public function releaseSavepoint($name)
+ {
+ $this->db->createCommand("RELEASE SAVEPOINT $name")->execute();
+ }
+
+ /**
+ * Rolls back to a previously created savepoint.
+ * @param string $name the savepoint name
+ */
+ public function rollBackSavepoint($name)
+ {
+ $this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute();
+ }
+
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.PDO-quote.php
+ */
+ public function quoteValue($str)
+ {
+ if (!is_string($str)) {
+ return $str;
+ }
+
+ $this->db->open();
+ if (($value = $this->db->pdo->quote($str)) !== false) {
+ return $value;
+ } else { // the driver doesn't support quote (e.g. oci)
+
+ return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
+ }
+ }
+
+ /**
+ * Quotes a table name for use in a query.
+ * If the table name contains schema prefix, the prefix will also be properly quoted.
+ * If the table name is already quoted or contains '(' or '{{',
+ * then this method will do nothing.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ * @see quoteSimpleTableName()
+ */
+ public function quoteTableName($name)
+ {
+ if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
+ return $name;
+ }
+ if (strpos($name, '.') === false) {
+ return $this->quoteSimpleTableName($name);
+ }
+ $parts = explode('.', $name);
+ foreach ($parts as $i => $part) {
+ $parts[$i] = $this->quoteSimpleTableName($part);
+ }
+
+ return implode('.', $parts);
+
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * If the column name contains prefix, the prefix will also be properly quoted.
+ * If the column name is already quoted or contains '(', '[[' or '{{',
+ * then this method will do nothing.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ * @see quoteSimpleColumnName()
+ */
+ public function quoteColumnName($name)
+ {
+ if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
+ return $name;
+ }
+ if (($pos = strrpos($name, '.')) !== false) {
+ $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
+ $name = substr($name, $pos + 1);
+ } else {
+ $prefix = '';
+ }
+
+ return $prefix . $this->quoteSimpleColumnName($name);
+ }
+
+ /**
+ * Quotes a simple table name for use in a query.
+ * A simple table name should contain the table name only without any schema prefix.
+ * If the table name is already quoted, this method will do nothing.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, "'") !== false ? $name : "'" . $name . "'";
+ }
+
+ /**
+ * Quotes a simple column name for use in a query.
+ * A simple column name should contain the column name only without any prefix.
+ * If the column name is already quoted or is the asterisk character '*', this method will do nothing.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
+ }
+
+ /**
+ * Returns the actual name of a given table name.
+ * This method will strip off curly brackets from the given table name
+ * and replace the percentage character '%' with [[Connection::tablePrefix]].
+ * @param string $name the table name to be converted
+ * @return string the real name of the given table name
+ */
+ public function getRawTableName($name)
+ {
+ if (strpos($name, '{{') !== false) {
+ $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
+
+ return str_replace('%', $this->db->tablePrefix, $name);
+ } else {
+ return $name;
+ }
+ }
+
+ /**
+ * Extracts the PHP type from abstract DB type.
+ * @param ColumnSchema $column the column schema information
+ * @return string PHP type name
+ */
+ protected function getColumnPhpType($column)
+ {
+ static $typeMap = [ // abstract type => php type
+ 'smallint' => 'integer',
+ 'integer' => 'integer',
+ 'boolean' => 'boolean',
+ 'float' => 'double',
+ ];
+ if (isset($typeMap[$column->type])) {
+ if ($column->type === 'integer') {
+ return $column->unsigned ? 'string' : 'integer';
+ } else {
+ return $typeMap[$column->type];
+ }
+ } else {
+ return 'string';
+ }
+ }
}
diff --git a/framework/db/StaleObjectException.php b/framework/db/StaleObjectException.php
index efa8ee9083e..386dbcf7de7 100644
--- a/framework/db/StaleObjectException.php
+++ b/framework/db/StaleObjectException.php
@@ -13,11 +13,11 @@
*/
class StaleObjectException extends Exception
{
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- return 'Stale Object Exception';
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return 'Stale Object Exception';
+ }
}
diff --git a/framework/db/TableSchema.php b/framework/db/TableSchema.php
index 83f431c1eff..86b925dc283 100644
--- a/framework/db/TableSchema.php
+++ b/framework/db/TableSchema.php
@@ -20,86 +20,85 @@
*/
class TableSchema extends Object
{
- /**
- * @var string the name of the schema that this table belongs to.
- */
- public $schemaName;
- /**
- * @var string the name of this table. The schema name is not included. Use [[fullName]] to get the name with schema name prefix.
- */
- public $name;
- /**
- * @var string the full name of this table, which includes the schema name prefix, if any.
- * Note that if the schema name is the same as the [[Schema::defaultSchema|default schema name]],
- * the schema name will not be included.
- */
- public $fullName;
- /**
- * @var string[] primary keys of this table.
- */
- public $primaryKey = [];
- /**
- * @var string sequence name for the primary key. Null if no sequence.
- */
- public $sequenceName;
- /**
- * @var array foreign keys of this table. Each array element is of the following structure:
- *
- * ~~~
- * [
- * 'ForeignTableName',
- * 'fk1' => 'pk1', // pk1 is in foreign table
- * 'fk2' => 'pk2', // if composite foreign key
- * ]
- * ~~~
- */
- public $foreignKeys = [];
- /**
- * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names.
- */
- public $columns = [];
+ /**
+ * @var string the name of the schema that this table belongs to.
+ */
+ public $schemaName;
+ /**
+ * @var string the name of this table. The schema name is not included. Use [[fullName]] to get the name with schema name prefix.
+ */
+ public $name;
+ /**
+ * @var string the full name of this table, which includes the schema name prefix, if any.
+ * Note that if the schema name is the same as the [[Schema::defaultSchema|default schema name]],
+ * the schema name will not be included.
+ */
+ public $fullName;
+ /**
+ * @var string[] primary keys of this table.
+ */
+ public $primaryKey = [];
+ /**
+ * @var string sequence name for the primary key. Null if no sequence.
+ */
+ public $sequenceName;
+ /**
+ * @var array foreign keys of this table. Each array element is of the following structure:
+ *
+ * ~~~
+ * [
+ * 'ForeignTableName',
+ * 'fk1' => 'pk1', // pk1 is in foreign table
+ * 'fk2' => 'pk2', // if composite foreign key
+ * ]
+ * ~~~
+ */
+ public $foreignKeys = [];
+ /**
+ * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names.
+ */
+ public $columns = [];
+ /**
+ * Gets the named column metadata.
+ * This is a convenient method for retrieving a named column even if it does not exist.
+ * @param string $name column name
+ * @return ColumnSchema metadata of the named column. Null if the named column does not exist.
+ */
+ public function getColumn($name)
+ {
+ return isset($this->columns[$name]) ? $this->columns[$name] : null;
+ }
- /**
- * Gets the named column metadata.
- * This is a convenient method for retrieving a named column even if it does not exist.
- * @param string $name column name
- * @return ColumnSchema metadata of the named column. Null if the named column does not exist.
- */
- public function getColumn($name)
- {
- return isset($this->columns[$name]) ? $this->columns[$name] : null;
- }
+ /**
+ * Returns the names of all columns in this table.
+ * @return array list of column names
+ */
+ public function getColumnNames()
+ {
+ return array_keys($this->columns);
+ }
- /**
- * Returns the names of all columns in this table.
- * @return array list of column names
- */
- public function getColumnNames()
- {
- return array_keys($this->columns);
- }
-
- /**
- * Manually specifies the primary key for this table.
- * @param string|array $keys the primary key (can be composite)
- * @throws InvalidParamException if the specified key cannot be found in the table.
- */
- public function fixPrimaryKey($keys)
- {
- if (!is_array($keys)) {
- $keys = [$keys];
- }
- $this->primaryKey = $keys;
- foreach ($this->columns as $column) {
- $column->isPrimaryKey = false;
- }
- foreach ($keys as $key) {
- if (isset($this->columns[$key])) {
- $this->columns[$key]->isPrimaryKey = true;
- } else {
- throw new InvalidParamException("Primary key '$key' cannot be found in table '{$this->name}'.");
- }
- }
- }
+ /**
+ * Manually specifies the primary key for this table.
+ * @param string|array $keys the primary key (can be composite)
+ * @throws InvalidParamException if the specified key cannot be found in the table.
+ */
+ public function fixPrimaryKey($keys)
+ {
+ if (!is_array($keys)) {
+ $keys = [$keys];
+ }
+ $this->primaryKey = $keys;
+ foreach ($this->columns as $column) {
+ $column->isPrimaryKey = false;
+ }
+ foreach ($keys as $key) {
+ if (isset($this->columns[$key])) {
+ $this->columns[$key]->isPrimaryKey = true;
+ } else {
+ throw new InvalidParamException("Primary key '$key' cannot be found in table '{$this->name}'.");
+ }
+ }
+ }
}
diff --git a/framework/db/Transaction.php b/framework/db/Transaction.php
index eefe7092925..d40c7d691ec 100644
--- a/framework/db/Transaction.php
+++ b/framework/db/Transaction.php
@@ -25,7 +25,7 @@
* $connection->createCommand($sql2)->execute();
* //.... other SQL executions
* $transaction->commit();
- * } catch(Exception $e) {
+ * } catch (Exception $e) {
* $transaction->rollBack();
* }
* ~~~
@@ -38,104 +38,107 @@
*/
class Transaction extends \yii\base\Object
{
- /**
- * @var Connection the database connection that this transaction is associated with.
- */
- public $db;
- /**
- * @var integer the nesting level of the transaction. 0 means the outermost level.
- */
- private $_level = 0;
-
- /**
- * Returns a value indicating whether this transaction is active.
- * @return boolean whether this transaction is active. Only an active transaction
- * can [[commit()]] or [[rollBack()]].
- */
- public function getIsActive()
- {
- return $this->_level > 0 && $this->db && $this->db->isActive;
- }
-
- /**
- * Begins a transaction.
- * @throws InvalidConfigException if [[db]] is `null`.
- */
- public function begin()
- {
- if ($this->db === null) {
- throw new InvalidConfigException('Transaction::db must be set.');
- }
- $this->db->open();
-
- if ($this->_level == 0) {
- Yii::trace('Begin transaction', __METHOD__);
- $this->db->pdo->beginTransaction();
- $this->_level = 1;
- return;
- }
-
- $schema = $this->db->getSchema();
- if ($schema->supportsSavepoint()) {
- Yii::trace('Set savepoint ' . $this->_level, __METHOD__);
- $schema->createSavepoint('LEVEL' . $this->_level);
- } else {
- Yii::info('Transaction not started: nested transaction not supported', __METHOD__);
- }
- $this->_level++;
- }
-
- /**
- * Commits a transaction.
- * @throws Exception if the transaction is not active
- */
- public function commit()
- {
- if (!$this->getIsActive()) {
- throw new Exception('Failed to commit transaction: transaction was inactive.');
- }
-
- $this->_level--;
- if ($this->_level == 0) {
- Yii::trace('Commit transaction', __METHOD__);
- $this->db->pdo->commit();
- return;
- }
-
- $schema = $this->db->getSchema();
- if ($schema->supportsSavepoint()) {
- Yii::trace('Release savepoint ' . $this->_level, __METHOD__);
- $schema->releaseSavepoint('LEVEL' . $this->_level);
- } else {
- Yii::info('Transaction not committed: nested transaction not supported', __METHOD__);
- }
- }
-
- /**
- * Rolls back a transaction.
- * @throws Exception if the transaction is not active
- */
- public function rollBack()
- {
- if (!$this->getIsActive()) {
- throw new Exception('Failed to roll back transaction: transaction was inactive.');
- }
-
- $this->_level--;
- if ($this->_level == 0) {
- Yii::trace('Roll back transaction', __METHOD__);
- $this->db->pdo->rollBack();
- return;
- }
-
- $schema = $this->db->getSchema();
- if ($schema->supportsSavepoint()) {
- Yii::trace('Roll back to savepoint ' . $this->_level, __METHOD__);
- $schema->rollBackSavepoint('LEVEL' . $this->_level);
- } else {
- Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__);
- // throw an exception to fail the outer transaction
- throw new Exception('Roll back failed: nested transaction not supported.');
- }
- }
+ /**
+ * @var Connection the database connection that this transaction is associated with.
+ */
+ public $db;
+ /**
+ * @var integer the nesting level of the transaction. 0 means the outermost level.
+ */
+ private $_level = 0;
+
+ /**
+ * Returns a value indicating whether this transaction is active.
+ * @return boolean whether this transaction is active. Only an active transaction
+ * can [[commit()]] or [[rollBack()]].
+ */
+ public function getIsActive()
+ {
+ return $this->_level > 0 && $this->db && $this->db->isActive;
+ }
+
+ /**
+ * Begins a transaction.
+ * @throws InvalidConfigException if [[db]] is `null`.
+ */
+ public function begin()
+ {
+ if ($this->db === null) {
+ throw new InvalidConfigException('Transaction::db must be set.');
+ }
+ $this->db->open();
+
+ if ($this->_level == 0) {
+ Yii::trace('Begin transaction', __METHOD__);
+ $this->db->pdo->beginTransaction();
+ $this->_level = 1;
+
+ return;
+ }
+
+ $schema = $this->db->getSchema();
+ if ($schema->supportsSavepoint()) {
+ Yii::trace('Set savepoint ' . $this->_level, __METHOD__);
+ $schema->createSavepoint('LEVEL' . $this->_level);
+ } else {
+ Yii::info('Transaction not started: nested transaction not supported', __METHOD__);
+ }
+ $this->_level++;
+ }
+
+ /**
+ * Commits a transaction.
+ * @throws Exception if the transaction is not active
+ */
+ public function commit()
+ {
+ if (!$this->getIsActive()) {
+ throw new Exception('Failed to commit transaction: transaction was inactive.');
+ }
+
+ $this->_level--;
+ if ($this->_level == 0) {
+ Yii::trace('Commit transaction', __METHOD__);
+ $this->db->pdo->commit();
+
+ return;
+ }
+
+ $schema = $this->db->getSchema();
+ if ($schema->supportsSavepoint()) {
+ Yii::trace('Release savepoint ' . $this->_level, __METHOD__);
+ $schema->releaseSavepoint('LEVEL' . $this->_level);
+ } else {
+ Yii::info('Transaction not committed: nested transaction not supported', __METHOD__);
+ }
+ }
+
+ /**
+ * Rolls back a transaction.
+ * @throws Exception if the transaction is not active
+ */
+ public function rollBack()
+ {
+ if (!$this->getIsActive()) {
+ throw new Exception('Failed to roll back transaction: transaction was inactive.');
+ }
+
+ $this->_level--;
+ if ($this->_level == 0) {
+ Yii::trace('Roll back transaction', __METHOD__);
+ $this->db->pdo->rollBack();
+
+ return;
+ }
+
+ $schema = $this->db->getSchema();
+ if ($schema->supportsSavepoint()) {
+ Yii::trace('Roll back to savepoint ' . $this->_level, __METHOD__);
+ $schema->rollBackSavepoint('LEVEL' . $this->_level);
+ } else {
+ Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__);
+ // throw an exception to fail the outer transaction
+ throw new Exception('Roll back failed: nested transaction not supported.');
+ }
+ }
}
diff --git a/framework/db/cubrid/QueryBuilder.php b/framework/db/cubrid/QueryBuilder.php
index 399599ec90e..24603faba13 100644
--- a/framework/db/cubrid/QueryBuilder.php
+++ b/framework/db/cubrid/QueryBuilder.php
@@ -17,74 +17,76 @@
*/
class QueryBuilder extends \yii\db\QueryBuilder
{
- /**
- * @var array mapping from abstract column types (keys) to physical column types (values).
- */
- public $typeMap = [
- Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY',
- Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY',
- Schema::TYPE_STRING => 'varchar(255)',
- Schema::TYPE_TEXT => 'varchar',
- Schema::TYPE_SMALLINT => 'smallint',
- Schema::TYPE_INTEGER => 'int',
- Schema::TYPE_BIGINT => 'bigint',
- Schema::TYPE_FLOAT => 'float(7)',
- Schema::TYPE_DECIMAL => 'decimal(10,0)',
- Schema::TYPE_DATETIME => 'datetime',
- Schema::TYPE_TIMESTAMP => 'timestamp',
- Schema::TYPE_TIME => 'time',
- Schema::TYPE_DATE => 'date',
- Schema::TYPE_BINARY => 'blob',
- Schema::TYPE_BOOLEAN => 'smallint',
- Schema::TYPE_MONEY => 'decimal(19,4)',
- ];
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = [
+ Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'varchar',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'int',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'float(7)',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'blob',
+ Schema::TYPE_BOOLEAN => 'smallint',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ ];
- /**
- * Creates a SQL statement for resetting the sequence value of a table's primary key.
- * The sequence will be reset such that the primary key of the next new row inserted
- * will have the specified value or 1.
- * @param string $tableName the name of the table whose primary key sequence will be reset
- * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
- * the next new row's primary key will have a value 1.
- * @return string the SQL statement for resetting sequence
- * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
- */
- public function resetSequence($tableName, $value = null)
- {
- $table = $this->db->getTableSchema($tableName);
- if ($table !== null && $table->sequenceName !== null) {
- $tableName = $this->db->quoteTableName($tableName);
- if ($value === null) {
- $key = reset($table->primaryKey);
- $value = (int)$this->db->createCommand("SELECT MAX(`$key`) FROM " . $this->db->schema->quoteTableName($tableName))->queryScalar() + 1;
- } else {
- $value = (int)$value;
- }
- return "ALTER TABLE " . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;";
- } elseif ($table === null) {
- throw new InvalidParamException("Table not found: $tableName");
- } else {
- throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
- }
- }
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $tableName the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function resetSequence($tableName, $value = null)
+ {
+ $table = $this->db->getTableSchema($tableName);
+ if ($table !== null && $table->sequenceName !== null) {
+ $tableName = $this->db->quoteTableName($tableName);
+ if ($value === null) {
+ $key = reset($table->primaryKey);
+ $value = (int) $this->db->createCommand("SELECT MAX(`$key`) FROM " . $this->db->schema->quoteTableName($tableName))->queryScalar() + 1;
+ } else {
+ $value = (int) $value;
+ }
- /**
- * @inheritdoc
- */
- public function buildLimit($limit, $offset)
- {
- $sql = '';
- // limit is not optional in CUBRID
- // http://www.cubrid.org/manual/90/en/LIMIT%20Clause
- // "You can specify a very big integer for row_count to display to the last row, starting from a specific row."
- if ($this->hasLimit($limit)) {
- $sql = 'LIMIT ' . $limit;
- if ($this->hasOffset($offset)) {
- $sql .= ' OFFSET ' . $offset;
- }
- } elseif ($this->hasOffset($offset)) {
- $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
- }
- return $sql;
- }
+ return "ALTER TABLE " . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;";
+ } elseif ($table === null) {
+ throw new InvalidParamException("Table not found: $tableName");
+ } else {
+ throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function buildLimit($limit, $offset)
+ {
+ $sql = '';
+ // limit is not optional in CUBRID
+ // http://www.cubrid.org/manual/90/en/LIMIT%20Clause
+ // "You can specify a very big integer for row_count to display to the last row, starting from a specific row."
+ if ($this->hasLimit($limit)) {
+ $sql = 'LIMIT ' . $limit;
+ if ($this->hasOffset($offset)) {
+ $sql .= ' OFFSET ' . $offset;
+ }
+ } elseif ($this->hasOffset($offset)) {
+ $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
+ }
+
+ return $sql;
+ }
}
diff --git a/framework/db/cubrid/Schema.php b/framework/db/cubrid/Schema.php
index 5c31997b46d..816882372ed 100644
--- a/framework/db/cubrid/Schema.php
+++ b/framework/db/cubrid/Schema.php
@@ -19,256 +19,257 @@
*/
class Schema extends \yii\db\Schema
{
- /**
- * @var array mapping from physical column types (keys) to abstract column types (values)
- * Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for
- * details on data types.
- */
- public $typeMap = [
- // Numeric data types
- 'short' => self::TYPE_SMALLINT,
- 'smallint' => self::TYPE_SMALLINT,
- 'int' => self::TYPE_INTEGER,
- 'integer' => self::TYPE_INTEGER,
- 'bigint' => self::TYPE_BIGINT,
- 'numeric' => self::TYPE_DECIMAL,
- 'decimal' => self::TYPE_DECIMAL,
- 'float' => self::TYPE_FLOAT,
- 'real' => self::TYPE_FLOAT,
- 'double' => self::TYPE_FLOAT,
- 'double precision' => self::TYPE_FLOAT,
- 'monetary' => self::TYPE_MONEY,
- // Date/Time data types
- 'date' => self::TYPE_DATE,
- 'time' => self::TYPE_TIME,
- 'timestamp' => self::TYPE_TIMESTAMP,
- 'datetime' => self::TYPE_DATETIME,
- // String data types
- 'char' => self::TYPE_STRING,
- 'varchar' => self::TYPE_STRING,
- 'char varying' => self::TYPE_STRING,
- 'nchar' => self::TYPE_STRING,
- 'nchar varying' => self::TYPE_STRING,
- 'string' => self::TYPE_STRING,
- // BLOB/CLOB data types
- 'blob' => self::TYPE_BINARY,
- 'clob' => self::TYPE_BINARY,
- // Bit string data types
- 'bit' => self::TYPE_STRING,
- 'bit varying' => self::TYPE_STRING,
- // Collection data types (considered strings for now)
- 'set' => self::TYPE_STRING,
- 'multiset' => self::TYPE_STRING,
- 'list' => self::TYPE_STRING,
- 'sequence' => self::TYPE_STRING,
- 'enum' => self::TYPE_STRING,
- ];
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ * Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for
+ * details on data types.
+ */
+ public $typeMap = [
+ // Numeric data types
+ 'short' => self::TYPE_SMALLINT,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'int' => self::TYPE_INTEGER,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'float' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+ 'double' => self::TYPE_FLOAT,
+ 'double precision' => self::TYPE_FLOAT,
+ 'monetary' => self::TYPE_MONEY,
+ // Date/Time data types
+ 'date' => self::TYPE_DATE,
+ 'time' => self::TYPE_TIME,
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'datetime' => self::TYPE_DATETIME,
+ // String data types
+ 'char' => self::TYPE_STRING,
+ 'varchar' => self::TYPE_STRING,
+ 'char varying' => self::TYPE_STRING,
+ 'nchar' => self::TYPE_STRING,
+ 'nchar varying' => self::TYPE_STRING,
+ 'string' => self::TYPE_STRING,
+ // BLOB/CLOB data types
+ 'blob' => self::TYPE_BINARY,
+ 'clob' => self::TYPE_BINARY,
+ // Bit string data types
+ 'bit' => self::TYPE_STRING,
+ 'bit varying' => self::TYPE_STRING,
+ // Collection data types (considered strings for now)
+ 'set' => self::TYPE_STRING,
+ 'multiset' => self::TYPE_STRING,
+ 'list' => self::TYPE_STRING,
+ 'sequence' => self::TYPE_STRING,
+ 'enum' => self::TYPE_STRING,
+ ];
+ /**
+ * @inheritdoc
+ */
+ public function releaseSavepoint($name)
+ {
+ // does nothing as cubrid does not support this
+ }
- /**
- * @inheritdoc
- */
- public function releaseSavepoint($name)
- {
- // does nothing as cubrid does not support this
- }
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, '"') !== false ? $name : '"' . $name . '"';
+ }
- /**
- * Quotes a table name for use in a query.
- * A simple table name has no schema prefix.
- * @param string $name table name
- * @return string the properly quoted table name
- */
- public function quoteSimpleTableName($name)
- {
- return strpos($name, '"') !== false ? $name : '"' . $name . '"';
- }
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
+ }
- /**
- * Quotes a column name for use in a query.
- * A simple column name has no prefix.
- * @param string $name column name
- * @return string the properly quoted column name
- */
- public function quoteSimpleColumnName($name)
- {
- return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
- }
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.PDO-quote.php
+ */
+ public function quoteValue($str)
+ {
+ if (!is_string($str)) {
+ return $str;
+ }
- /**
- * Quotes a string value for use in a query.
- * Note that if the parameter is not a string, it will be returned without change.
- * @param string $str string to be quoted
- * @return string the properly quoted string
- * @see http://www.php.net/manual/en/function.PDO-quote.php
- */
- public function quoteValue($str)
- {
- if (!is_string($str)) {
- return $str;
- }
+ $this->db->open();
+ // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658
+ $version = $this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION);
+ if (version_compare($version, '8.4.4.0002', '<') || $version[0] == '9' && version_compare($version, '9.2.0.0002', '<=')) {
+ return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
+ } else {
+ return $this->db->pdo->quote($str);
+ }
+ }
- $this->db->open();
- // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658
- $version = $this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION);
- if (version_compare($version, '8.4.4.0002', '<') || $version[0] == '9' && version_compare($version, '9.2.0.0002', '<=')) {
- return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
- } else {
- return $this->db->pdo->quote($str);
- }
- }
+ /**
+ * Creates a query builder for the CUBRID database.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
- /**
- * Creates a query builder for the CUBRID database.
- * @return QueryBuilder query builder instance
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema driver dependent table metadata. Null if the table does not exist.
+ */
+ protected function loadTableSchema($name)
+ {
+ $this->db->open();
+ $tableInfo = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name);
- /**
- * Loads the metadata for the specified table.
- * @param string $name table name
- * @return TableSchema driver dependent table metadata. Null if the table does not exist.
- */
- protected function loadTableSchema($name)
- {
- $this->db->open();
- $tableInfo = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name);
+ if (!isset($tableInfo[0]['NAME'])) {
+ return null;
+ }
- if (!isset($tableInfo[0]['NAME'])) {
- return null;
- }
+ $table = new TableSchema();
+ $table->fullName = $table->name = $tableInfo[0]['NAME'];
- $table = new TableSchema();
- $table->fullName = $table->name = $tableInfo[0]['NAME'];
+ $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
+ $columns = $this->db->createCommand($sql)->queryAll();
- $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
- $columns = $this->db->createCommand($sql)->queryAll();
+ foreach ($columns as $info) {
+ $column = $this->loadColumnSchema($info);
+ $table->columns[$column->name] = $column;
+ }
- foreach ($columns as $info) {
- $column = $this->loadColumnSchema($info);
- $table->columns[$column->name] = $column;
- }
+ $primaryKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $table->name);
+ foreach ($primaryKeys as $key) {
+ $column = $table->columns[$key['ATTR_NAME']];
+ $column->isPrimaryKey = true;
+ $table->primaryKey[] = $column->name;
+ if ($column->autoIncrement) {
+ $table->sequenceName = '';
+ }
+ }
- $primaryKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $table->name);
- foreach ($primaryKeys as $key) {
- $column = $table->columns[$key['ATTR_NAME']];
- $column->isPrimaryKey = true;
- $table->primaryKey[] = $column->name;
- if ($column->autoIncrement) {
- $table->sequenceName = '';
- }
- }
+ $foreignKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name);
+ foreach ($foreignKeys as $key) {
+ if (isset($table->foreignKeys[$key['FK_NAME']])) {
+ $table->foreignKeys[$key['FK_NAME']][$key['FKCOLUMN_NAME']] = $key['PKCOLUMN_NAME'];
+ } else {
+ $table->foreignKeys[$key['FK_NAME']] = [
+ $key['PKTABLE_NAME'],
+ $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME']
+ ];
+ }
+ }
+ $table->foreignKeys = array_values($table->foreignKeys);
- $foreignKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name);
- foreach ($foreignKeys as $key) {
- if (isset($table->foreignKeys[$key['FK_NAME']])) {
- $table->foreignKeys[$key['FK_NAME']][$key['FKCOLUMN_NAME']] = $key['PKCOLUMN_NAME'];
- } else {
- $table->foreignKeys[$key['FK_NAME']] = [
- $key['PKTABLE_NAME'],
- $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME']
- ];
- }
- }
- $table->foreignKeys = array_values($table->foreignKeys);
+ return $table;
+ }
- return $table;
- }
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema();
- /**
- * Loads the column information into a [[ColumnSchema]] object.
- * @param array $info column information
- * @return ColumnSchema the column schema object
- */
- protected function loadColumnSchema($info)
- {
- $column = new ColumnSchema();
+ $column->name = $info['Field'];
+ $column->allowNull = $info['Null'] === 'YES';
+ $column->isPrimaryKey = false; // primary key will be set by loadTableSchema() later
+ $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
- $column->name = $info['Field'];
- $column->allowNull = $info['Null'] === 'YES';
- $column->isPrimaryKey = false; // primary key will be set by loadTableSchema() later
- $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
+ $column->dbType = strtolower($info['Type']);
+ $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
- $column->dbType = strtolower($info['Type']);
- $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = $matches[1];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+ if (!empty($matches[2])) {
+ if ($type === 'enum') {
+ $values = explode(',', $matches[2]);
+ foreach ($values as $i => $value) {
+ $values[$i] = trim($value, "'");
+ }
+ $column->enumValues = $values;
+ } else {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int) $values[0];
+ if (isset($values[1])) {
+ $column->scale = (int) $values[1];
+ }
+ }
+ }
+ }
- $column->type = self::TYPE_STRING;
- if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
- $type = $matches[1];
- if (isset($this->typeMap[$type])) {
- $column->type = $this->typeMap[$type];
- }
- if (!empty($matches[2])) {
- if ($type === 'enum') {
- $values = explode(',', $matches[2]);
- foreach ($values as $i => $value) {
- $values[$i] = trim($value, "'");
- }
- $column->enumValues = $values;
- } else {
- $values = explode(',', $matches[2]);
- $column->size = $column->precision = (int)$values[0];
- if (isset($values[1])) {
- $column->scale = (int)$values[1];
- }
- }
- }
- }
+ $column->phpType = $this->getColumnPhpType($column);
- $column->phpType = $this->getColumnPhpType($column);
+ if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP' ||
+ $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' ||
+ $column->type === 'date' && $info['Default'] === 'SYS_DATE' ||
+ $column->type === 'time' && $info['Default'] === 'SYS_TIME'
+ ) {
+ $column->defaultValue = new Expression($info['Default']);
+ } else {
+ $column->defaultValue = $column->typecast($info['Default']);
+ }
- if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP' ||
- $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' ||
- $column->type === 'date' && $info['Default'] === 'SYS_DATE' ||
- $column->type === 'time' && $info['Default'] === 'SYS_TIME'
- ) {
- $column->defaultValue = new Expression($info['Default']);
- } else {
- $column->defaultValue = $column->typecast($info['Default']);
- }
+ return $column;
+ }
- return $column;
- }
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ $this->db->open();
+ $tables = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE);
+ $tableNames = [];
+ foreach ($tables as $table) {
+ // do not list system tables
+ if ($table['TYPE'] != 0) {
+ $tableNames[] = $table['NAME'];
+ }
+ }
- /**
- * Returns all table names in the database.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @return array all table names in the database. The names have NO schema name prefix.
- */
- protected function findTableNames($schema = '')
- {
- $this->db->open();
- $tables = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE);
- $tableNames = [];
- foreach ($tables as $table) {
- // do not list system tables
- if ($table['TYPE'] != 0) {
- $tableNames[] = $table['NAME'];
- }
- }
- return $tableNames;
- }
+ return $tableNames;
+ }
- /**
- * Determines the PDO type for the given PHP data value.
- * @param mixed $data the data whose PDO type is to be determined
- * @return integer the PDO type
- * @see http://www.php.net/manual/en/pdo.constants.php
- */
- public function getPdoType($data)
- {
- static $typeMap = [
- // php type => PDO type
- 'boolean' => \PDO::PARAM_INT, // PARAM_BOOL is not supported by CUBRID PDO
- 'integer' => \PDO::PARAM_INT,
- 'string' => \PDO::PARAM_STR,
- 'resource' => \PDO::PARAM_LOB,
- 'NULL' => \PDO::PARAM_NULL,
- ];
- $type = gettype($data);
- return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
- }
+ /**
+ * Determines the PDO type for the given PHP data value.
+ * @param mixed $data the data whose PDO type is to be determined
+ * @return integer the PDO type
+ * @see http://www.php.net/manual/en/pdo.constants.php
+ */
+ public function getPdoType($data)
+ {
+ static $typeMap = [
+ // php type => PDO type
+ 'boolean' => \PDO::PARAM_INT, // PARAM_BOOL is not supported by CUBRID PDO
+ 'integer' => \PDO::PARAM_INT,
+ 'string' => \PDO::PARAM_STR,
+ 'resource' => \PDO::PARAM_LOB,
+ 'NULL' => \PDO::PARAM_NULL,
+ ];
+ $type = gettype($data);
+
+ return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
+ }
}
diff --git a/framework/db/mssql/PDO.php b/framework/db/mssql/PDO.php
index 59c2455ddc0..dc6bb3dbbe7 100644
--- a/framework/db/mssql/PDO.php
+++ b/framework/db/mssql/PDO.php
@@ -16,46 +16,49 @@
*/
class PDO extends \PDO
{
- /**
- * Returns value of the last inserted ID.
- * @param string|null $sequence the sequence name. Defaults to null.
- * @return integer last inserted ID value.
- */
- public function lastInsertId($sequence = null)
- {
- return $this->query('SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS bigint)')->fetchColumn();
- }
-
- /**
- * Starts a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
- * natively support transactions.
- * @return boolean the result of a transaction start.
- */
- public function beginTransaction()
- {
- $this->exec('BEGIN TRANSACTION');
- return true;
- }
-
- /**
- * Commits a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
- * natively support transactions.
- * @return boolean the result of a transaction commit.
- */
- public function commit()
- {
- $this->exec('COMMIT TRANSACTION');
- return true;
- }
-
- /**
- * Rollbacks a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
- * natively support transactions.
- * @return boolean the result of a transaction roll back.
- */
- public function rollBack()
- {
- $this->exec('ROLLBACK TRANSACTION');
- return true;
- }
+ /**
+ * Returns value of the last inserted ID.
+ * @param string|null $sequence the sequence name. Defaults to null.
+ * @return integer last inserted ID value.
+ */
+ public function lastInsertId($sequence = null)
+ {
+ return $this->query('SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS bigint)')->fetchColumn();
+ }
+
+ /**
+ * Starts a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
+ * natively support transactions.
+ * @return boolean the result of a transaction start.
+ */
+ public function beginTransaction()
+ {
+ $this->exec('BEGIN TRANSACTION');
+
+ return true;
+ }
+
+ /**
+ * Commits a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
+ * natively support transactions.
+ * @return boolean the result of a transaction commit.
+ */
+ public function commit()
+ {
+ $this->exec('COMMIT TRANSACTION');
+
+ return true;
+ }
+
+ /**
+ * Rollbacks a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
+ * natively support transactions.
+ * @return boolean the result of a transaction roll back.
+ */
+ public function rollBack()
+ {
+ $this->exec('ROLLBACK TRANSACTION');
+
+ return true;
+ }
}
diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php
index 2eb56a6a6a2..10c7ca4f1df 100644
--- a/framework/db/mssql/QueryBuilder.php
+++ b/framework/db/mssql/QueryBuilder.php
@@ -17,27 +17,27 @@
*/
class QueryBuilder extends \yii\db\QueryBuilder
{
- /**
- * @var array mapping from abstract column types (keys) to physical column types (values).
- */
- public $typeMap = [
- Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY',
- Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY',
- Schema::TYPE_STRING => 'varchar(255)',
- Schema::TYPE_TEXT => 'text',
- Schema::TYPE_SMALLINT => 'smallint',
- Schema::TYPE_INTEGER => 'int',
- Schema::TYPE_BIGINT => 'bigint',
- Schema::TYPE_FLOAT => 'float',
- Schema::TYPE_DECIMAL => 'decimal',
- Schema::TYPE_DATETIME => 'datetime',
- Schema::TYPE_TIMESTAMP => 'timestamp',
- Schema::TYPE_TIME => 'time',
- Schema::TYPE_DATE => 'date',
- Schema::TYPE_BINARY => 'binary',
- Schema::TYPE_BOOLEAN => 'bit',
- Schema::TYPE_MONEY => 'decimal(19,4)',
- ];
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = [
+ Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'int',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'float',
+ Schema::TYPE_DECIMAL => 'decimal',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'binary',
+ Schema::TYPE_BOOLEAN => 'bit',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ ];
// public function update($table, $columns, $condition, &$params)
// {
@@ -49,91 +49,94 @@ class QueryBuilder extends \yii\db\QueryBuilder
// return '';
// }
- /**
- * @param integer $limit
- * @param integer $offset
- * @return string the LIMIT and OFFSET clauses built from [[\yii\db\Query::$limit]].
- */
- public function buildLimit($limit, $offset = 0)
- {
- $hasOffset = $this->hasOffset($offset);
- $hasLimit = $this->hasLimit($limit);
- if ($hasOffset || $hasLimit) {
- // http://technet.microsoft.com/en-us/library/gg699618.aspx
- $sql = 'OFFSET ' . ($hasOffset ? $offset : '0');
- if ($hasLimit) {
- $sql .= " FETCH NEXT $limit ROWS ONLY";
- }
- return $sql;
- } else {
- return '';
- }
- }
+ /**
+ * @param integer $limit
+ * @param integer $offset
+ * @return string the LIMIT and OFFSET clauses built from [[\yii\db\Query::$limit]].
+ */
+ public function buildLimit($limit, $offset = 0)
+ {
+ $hasOffset = $this->hasOffset($offset);
+ $hasLimit = $this->hasLimit($limit);
+ if ($hasOffset || $hasLimit) {
+ // http://technet.microsoft.com/en-us/library/gg699618.aspx
+ $sql = 'OFFSET ' . ($hasOffset ? $offset : '0');
+ if ($hasLimit) {
+ $sql .= " FETCH NEXT $limit ROWS ONLY";
+ }
+
+ return $sql;
+ } else {
+ return '';
+ }
+ }
// public function resetSequence($table, $value = null)
// {
// return '';
// }
- /**
- * Builds a SQL statement for renaming a DB table.
- * @param string $table the table to be renamed. The name will be properly quoted by the method.
- * @param string $newName the new table name. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB table.
- */
- public function renameTable($table, $newName)
- {
- return "sp_rename '$table', '$newName'";
- }
+ /**
+ * Builds a SQL statement for renaming a DB table.
+ * @param string $table the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB table.
+ */
+ public function renameTable($table, $newName)
+ {
+ return "sp_rename '$table', '$newName'";
+ }
+
+ /**
+ * Builds a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $name the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB column.
+ */
+ public function renameColumn($table, $name, $newName)
+ {
+ return "sp_rename '$table.$name', '$newName', 'COLUMN'";
+ }
+
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ $type = $this->getColumnType($type);
+ $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
+ . $this->db->quoteColumnName($column) . ' '
+ . $this->getColumnType($type);
- /**
- * Builds a SQL statement for renaming a column.
- * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
- * @param string $name the old name of the column. The name will be properly quoted by the method.
- * @param string $newName the new name of the column. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB column.
- */
- public function renameColumn($table, $name, $newName)
- {
- return "sp_rename '$table.$name', '$newName', 'COLUMN'";
- }
+ return $sql;
+ }
- /**
- * Builds a SQL statement for changing the definition of a column.
- * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
- * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
- * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
- * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
- * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
- * @return string the SQL statement for changing the definition of a column.
- */
- public function alterColumn($table, $column, $type)
- {
- $type = $this->getColumnType($type);
- $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
- . $this->db->quoteColumnName($column) . ' '
- . $this->getColumnType($type);
- return $sql;
- }
+ /**
+ * Builds a SQL statement for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
+ * @return string the SQL statement for checking integrity
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ if ($schema !== '') {
+ $table = "{$schema}.{$table}";
+ }
+ $table = $this->db->quoteTableName($table);
+ if ($this->db->getTableSchema($table) === null) {
+ throw new InvalidParamException("Table not found: $table");
+ }
+ $enable = $check ? 'CHECK' : 'NOCHECK';
- /**
- * Builds a SQL statement for enabling or disabling integrity check.
- * @param boolean $check whether to turn on or off the integrity check.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
- * @return string the SQL statement for checking integrity
- * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
- */
- public function checkIntegrity($check = true, $schema = '', $table = '')
- {
- if ($schema !== '') {
- $table = "{$schema}.{$table}";
- }
- $table = $this->db->quoteTableName($table);
- if ($this->db->getTableSchema($table) === null) {
- throw new InvalidParamException("Table not found: $table");
- }
- $enable = $check ? 'CHECK' : 'NOCHECK';
- return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL";
- }
+ return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL";
+ }
}
diff --git a/framework/db/mssql/Schema.php b/framework/db/mssql/Schema.php
index 4b3b1da5911..d73b918be66 100644
--- a/framework/db/mssql/Schema.php
+++ b/framework/db/mssql/Schema.php
@@ -17,362 +17,364 @@
*/
class Schema extends \yii\db\Schema
{
- /**
- * @var string the default schema used for the current session.
- */
- public $defaultSchema = 'dbo';
- /**
- * @var array mapping from physical column types (keys) to abstract column types (values)
- */
- public $typeMap = [
- // exact numbers
- 'bigint' => self::TYPE_BIGINT,
- 'numeric' => self::TYPE_DECIMAL,
- 'bit' => self::TYPE_SMALLINT,
- 'smallint' => self::TYPE_SMALLINT,
- 'decimal' => self::TYPE_DECIMAL,
- 'smallmoney' => self::TYPE_MONEY,
- 'int' => self::TYPE_INTEGER,
- 'tinyint' => self::TYPE_SMALLINT,
- 'money' => self::TYPE_MONEY,
-
- // approximate numbers
- 'float' => self::TYPE_FLOAT,
- 'real' => self::TYPE_FLOAT,
-
- // date and time
- 'date' => self::TYPE_DATE,
- 'datetimeoffset' => self::TYPE_DATETIME,
- 'datetime2' => self::TYPE_DATETIME,
- 'smalldatetime' => self::TYPE_DATETIME,
- 'datetime' => self::TYPE_DATETIME,
- 'time' => self::TYPE_TIME,
-
- // character strings
- 'char' => self::TYPE_STRING,
- 'varchar' => self::TYPE_STRING,
- 'text' => self::TYPE_TEXT,
-
- // unicode character strings
- 'nchar' => self::TYPE_STRING,
- 'nvarchar' => self::TYPE_STRING,
- 'ntext' => self::TYPE_TEXT,
-
- // binary strings
- 'binary' => self::TYPE_BINARY,
- 'varbinary' => self::TYPE_BINARY,
- 'image' => self::TYPE_BINARY,
-
- // other data types
- // 'cursor' type cannot be used with tables
- 'timestamp' => self::TYPE_TIMESTAMP,
- 'hierarchyid' => self::TYPE_STRING,
- 'uniqueidentifier' => self::TYPE_STRING,
- 'sql_variant' => self::TYPE_STRING,
- 'xml' => self::TYPE_STRING,
- 'table' => self::TYPE_STRING,
- ];
-
- /**
- * @inheritdoc
- */
- public function createSavepoint($name)
- {
- $this->db->createCommand("SAVE TRANSACTION $name")->execute();
- }
-
- /**
- * @inheritdoc
- */
- public function releaseSavepoint($name)
- {
- // does nothing as MSSQL does not support this
- }
-
- /**
- * @inheritdoc
- */
- public function rollBackSavepoint($name)
- {
- $this->db->createCommand("ROLLBACK TRANSACTION $name")->execute();
- }
-
- /**
- * Quotes a table name for use in a query.
- * A simple table name has no schema prefix.
- * @param string $name table name.
- * @return string the properly quoted table name.
- */
- public function quoteSimpleTableName($name)
- {
- return strpos($name, '[') === false ? "[{$name}]" : $name;
- }
-
- /**
- * Quotes a column name for use in a query.
- * A simple column name has no prefix.
- * @param string $name column name.
- * @return string the properly quoted column name.
- */
- public function quoteSimpleColumnName($name)
- {
- return strpos($name, '[') === false && $name !== '*' ? "[{$name}]" : $name;
- }
-
- /**
- * Creates a query builder for the MSSQL database.
- * @return QueryBuilder query builder interface.
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
-
- /**
- * Loads the metadata for the specified table.
- * @param string $name table name
- * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
- */
- public function loadTableSchema($name)
- {
- $table = new TableSchema();
- $this->resolveTableNames($table, $name);
- $this->findPrimaryKeys($table);
- if ($this->findColumns($table)) {
- $this->findForeignKeys($table);
- return $table;
- } else {
- return null;
- }
- }
-
- /**
- * Resolves the table name and schema name (if any).
- * @param TableSchema $table the table metadata object
- * @param string $name the table name
- */
- protected function resolveTableNames($table, $name)
- {
- $parts = explode('.', str_replace(['[', ']'], '', $name));
- $partCount = count($parts);
- if ($partCount == 3) {
- // catalog name, schema name and table name passed
- $table->catalogName = $parts[0];
- $table->schemaName = $parts[1];
- $table->name = $parts[2];
- $table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name;
- } elseif ($partCount == 2) {
- // only schema name and table name passed
- $table->schemaName = $parts[0];
- $table->name = $parts[1];
- $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
- } else {
- // only table name passed
- $table->schemaName = $this->defaultSchema;
- $table->fullName = $table->name = $parts[0];
- }
- }
-
- /**
- * Loads the column information into a [[ColumnSchema]] object.
- * @param array $info column information
- * @return ColumnSchema the column schema object
- */
- protected function loadColumnSchema($info)
- {
- $column = new ColumnSchema();
-
- $column->name = $info['column_name'];
- $column->allowNull = $info['is_nullable'] == 'YES';
- $column->dbType = $info['data_type'];
- $column->enumValues = []; // mssql has only vague equivalents to enum
- $column->isPrimaryKey = null; // primary key will be determined in findColumns() method
- $column->autoIncrement = $info['is_identity'] == 1;
- $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
- $column->comment = $info['comment'] === null ? '' : $info['comment'];
-
- $column->type = self::TYPE_STRING;
- if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
- $type = $matches[1];
- if (isset($this->typeMap[$type])) {
- $column->type = $this->typeMap[$type];
- }
- if (!empty($matches[2])) {
- $values = explode(',', $matches[2]);
- $column->size = $column->precision = (int)$values[0];
- if (isset($values[1])) {
- $column->scale = (int)$values[1];
- }
- if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
- $column->type = 'boolean';
- } elseif ($type === 'bit') {
- if ($column->size > 32) {
- $column->type = 'bigint';
- } elseif ($column->size === 32) {
- $column->type = 'integer';
- }
- }
- }
- }
-
- $column->phpType = $this->getColumnPhpType($column);
-
- if ($info['column_default'] == '(NULL)') {
- $info['column_default'] = null;
- }
- if ($column->type !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP') {
- $column->defaultValue = $column->typecast($info['column_default']);
- }
-
- return $column;
- }
-
- /**
- * Collects the metadata of table columns.
- * @param TableSchema $table the table metadata
- * @return boolean whether the table exists in the database
- */
- protected function findColumns($table)
- {
- $columnsTableName = 'information_schema.columns';
- $whereSql = "[t1].[table_name] = '{$table->name}'";
- if ($table->catalogName !== null) {
- $columnsTableName = "{$table->catalogName}.{$columnsTableName}";
- $whereSql .= " AND [t1].[table_catalog] = '{$table->catalogName}'";
- }
- if ($table->schemaName !== null) {
- $whereSql .= " AND [t1].[table_schema] = '{$table->schemaName}'";
- }
- $columnsTableName = $this->quoteTableName($columnsTableName);
-
- $sql = << self::TYPE_BIGINT,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'bit' => self::TYPE_SMALLINT,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'smallmoney' => self::TYPE_MONEY,
+ 'int' => self::TYPE_INTEGER,
+ 'tinyint' => self::TYPE_SMALLINT,
+ 'money' => self::TYPE_MONEY,
+
+ // approximate numbers
+ 'float' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+
+ // date and time
+ 'date' => self::TYPE_DATE,
+ 'datetimeoffset' => self::TYPE_DATETIME,
+ 'datetime2' => self::TYPE_DATETIME,
+ 'smalldatetime' => self::TYPE_DATETIME,
+ 'datetime' => self::TYPE_DATETIME,
+ 'time' => self::TYPE_TIME,
+
+ // character strings
+ 'char' => self::TYPE_STRING,
+ 'varchar' => self::TYPE_STRING,
+ 'text' => self::TYPE_TEXT,
+
+ // unicode character strings
+ 'nchar' => self::TYPE_STRING,
+ 'nvarchar' => self::TYPE_STRING,
+ 'ntext' => self::TYPE_TEXT,
+
+ // binary strings
+ 'binary' => self::TYPE_BINARY,
+ 'varbinary' => self::TYPE_BINARY,
+ 'image' => self::TYPE_BINARY,
+
+ // other data types
+ // 'cursor' type cannot be used with tables
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'hierarchyid' => self::TYPE_STRING,
+ 'uniqueidentifier' => self::TYPE_STRING,
+ 'sql_variant' => self::TYPE_STRING,
+ 'xml' => self::TYPE_STRING,
+ 'table' => self::TYPE_STRING,
+ ];
+
+ /**
+ * @inheritdoc
+ */
+ public function createSavepoint($name)
+ {
+ $this->db->createCommand("SAVE TRANSACTION $name")->execute();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function releaseSavepoint($name)
+ {
+ // does nothing as MSSQL does not support this
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rollBackSavepoint($name)
+ {
+ $this->db->createCommand("ROLLBACK TRANSACTION $name")->execute();
+ }
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name.
+ * @return string the properly quoted table name.
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, '[') === false ? "[{$name}]" : $name;
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name.
+ * @return string the properly quoted column name.
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '[') === false && $name !== '*' ? "[{$name}]" : $name;
+ }
+
+ /**
+ * Creates a query builder for the MSSQL database.
+ * @return QueryBuilder query builder interface.
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
+ */
+ public function loadTableSchema($name)
+ {
+ $table = new TableSchema();
+ $this->resolveTableNames($table, $name);
+ $this->findPrimaryKeys($table);
+ if ($this->findColumns($table)) {
+ $this->findForeignKeys($table);
+
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Resolves the table name and schema name (if any).
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace(['[', ']'], '', $name));
+ $partCount = count($parts);
+ if ($partCount == 3) {
+ // catalog name, schema name and table name passed
+ $table->catalogName = $parts[0];
+ $table->schemaName = $parts[1];
+ $table->name = $parts[2];
+ $table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name;
+ } elseif ($partCount == 2) {
+ // only schema name and table name passed
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
+ } else {
+ // only table name passed
+ $table->schemaName = $this->defaultSchema;
+ $table->fullName = $table->name = $parts[0];
+ }
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema();
+
+ $column->name = $info['column_name'];
+ $column->allowNull = $info['is_nullable'] == 'YES';
+ $column->dbType = $info['data_type'];
+ $column->enumValues = []; // mssql has only vague equivalents to enum
+ $column->isPrimaryKey = null; // primary key will be determined in findColumns() method
+ $column->autoIncrement = $info['is_identity'] == 1;
+ $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
+ $column->comment = $info['comment'] === null ? '' : $info['comment'];
+
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = $matches[1];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+ if (!empty($matches[2])) {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int) $values[0];
+ if (isset($values[1])) {
+ $column->scale = (int) $values[1];
+ }
+ if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
+ $column->type = 'boolean';
+ } elseif ($type === 'bit') {
+ if ($column->size > 32) {
+ $column->type = 'bigint';
+ } elseif ($column->size === 32) {
+ $column->type = 'integer';
+ }
+ }
+ }
+ }
+
+ $column->phpType = $this->getColumnPhpType($column);
+
+ if ($info['column_default'] == '(NULL)') {
+ $info['column_default'] = null;
+ }
+ if ($column->type !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP') {
+ $column->defaultValue = $column->typecast($info['column_default']);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Collects the metadata of table columns.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ $columnsTableName = 'information_schema.columns';
+ $whereSql = "[t1].[table_name] = '{$table->name}'";
+ if ($table->catalogName !== null) {
+ $columnsTableName = "{$table->catalogName}.{$columnsTableName}";
+ $whereSql .= " AND [t1].[table_catalog] = '{$table->catalogName}'";
+ }
+ if ($table->schemaName !== null) {
+ $whereSql .= " AND [t1].[table_schema] = '{$table->schemaName}'";
+ }
+ $columnsTableName = $this->quoteTableName($columnsTableName);
+
+ $sql = <<db->createCommand($sql)->queryAll();
- } catch (\Exception $e) {
- return false;
- }
- foreach ($columns as $column) {
- $column = $this->loadColumnSchema($column);
- foreach ($table->primaryKey as $primaryKey) {
- if (strcasecmp($column->name, $primaryKey) === 0) {
- $column->isPrimaryKey = true;
- break;
- }
- }
- if ($column->isPrimaryKey && $column->autoIncrement) {
- $table->sequenceName = '';
- }
- $table->columns[$column->name] = $column;
- }
- return true;
- }
-
- /**
- * Collects the primary key column details for the given table.
- * @param TableSchema $table the table metadata
- */
- protected function findPrimaryKeys($table)
- {
- $keyColumnUsageTableName = 'information_schema.key_column_usage';
- $tableConstraintsTableName = 'information_schema.table_constraints';
- if ($table->catalogName !== null) {
- $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName;
- $tableConstraintsTableName = $table->catalogName . '.' . $tableConstraintsTableName;
- }
- $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName);
- $tableConstraintsTableName = $this->quoteTableName($tableConstraintsTableName);
-
- $sql = <<db->createCommand($sql)->queryAll();
+ } catch (\Exception $e) {
+ return false;
+ }
+ foreach ($columns as $column) {
+ $column = $this->loadColumnSchema($column);
+ foreach ($table->primaryKey as $primaryKey) {
+ if (strcasecmp($column->name, $primaryKey) === 0) {
+ $column->isPrimaryKey = true;
+ break;
+ }
+ }
+ if ($column->isPrimaryKey && $column->autoIncrement) {
+ $table->sequenceName = '';
+ }
+ $table->columns[$column->name] = $column;
+ }
+
+ return true;
+ }
+
+ /**
+ * Collects the primary key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findPrimaryKeys($table)
+ {
+ $keyColumnUsageTableName = 'information_schema.key_column_usage';
+ $tableConstraintsTableName = 'information_schema.table_constraints';
+ if ($table->catalogName !== null) {
+ $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName;
+ $tableConstraintsTableName = $table->catalogName . '.' . $tableConstraintsTableName;
+ }
+ $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName);
+ $tableConstraintsTableName = $this->quoteTableName($tableConstraintsTableName);
+
+ $sql = <<primaryKey = $this->db
- ->createCommand($sql, [':tableName' => $table->name, ':schemaName' => $table->schemaName])
- ->queryColumn();
- }
-
- /**
- * Collects the foreign key column details for the given table.
- * @param TableSchema $table the table metadata
- */
- protected function findForeignKeys($table)
- {
- $referentialConstraintsTableName = 'information_schema.referential_constraints';
- $keyColumnUsageTableName = 'information_schema.key_column_usage';
- if ($table->catalogName !== null) {
- $referentialConstraintsTableName = $table->catalogName . '.' . $referentialConstraintsTableName;
- $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName;
- }
- $referentialConstraintsTableName = $this->quoteTableName($referentialConstraintsTableName);
- $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName);
-
- // please refer to the following page for more details:
- // http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx
- $sql = <<primaryKey = $this->db
+ ->createCommand($sql, [':tableName' => $table->name, ':schemaName' => $table->schemaName])
+ ->queryColumn();
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findForeignKeys($table)
+ {
+ $referentialConstraintsTableName = 'information_schema.referential_constraints';
+ $keyColumnUsageTableName = 'information_schema.key_column_usage';
+ if ($table->catalogName !== null) {
+ $referentialConstraintsTableName = $table->catalogName . '.' . $referentialConstraintsTableName;
+ $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName;
+ }
+ $referentialConstraintsTableName = $this->quoteTableName($referentialConstraintsTableName);
+ $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName);
+
+ // please refer to the following page for more details:
+ // http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx
+ $sql = <<db->createCommand($sql, [':tableName' => $table->name])->queryAll();
- $table->foreignKeys = [];
- foreach ($rows as $row) {
- $table->foreignKeys[] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']];
- }
- }
-
- /**
- * Returns all table names in the database.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @return array all table names in the database. The names have NO schema name prefix.
- */
- protected function findTableNames($schema = '')
- {
- if ($schema === '') {
- $schema = $this->defaultSchema;
- }
-
- $sql = <<db->createCommand($sql, [':tableName' => $table->name])->queryAll();
+ $table->foreignKeys = [];
+ foreach ($rows as $row) {
+ $table->foreignKeys[] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']];
+ }
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ if ($schema === '') {
+ $schema = $this->defaultSchema;
+ }
+
+ $sql = <<db->createCommand($sql, [':schema' => $schema])->queryColumn();
- }
+ return $this->db->createCommand($sql, [':schema' => $schema])->queryColumn();
+ }
}
diff --git a/framework/db/mssql/SqlsrvPDO.php b/framework/db/mssql/SqlsrvPDO.php
index 29444c55caa..1f29b4a682f 100644
--- a/framework/db/mssql/SqlsrvPDO.php
+++ b/framework/db/mssql/SqlsrvPDO.php
@@ -16,18 +16,18 @@
*/
class SqlsrvPDO extends \PDO
{
- /**
- * Returns value of the last inserted ID.
- *
- * SQLSRV driver implements [[PDO::lastInsertId()]] method but with a single peculiarity:
- * when `$sequence` value is a null or an empty string it returns an empty string.
- * But when parameter is not specified it works as expected and returns actual
- * last inserted ID (like the other PDO drivers).
- * @param string|null $sequence the sequence name. Defaults to null.
- * @return integer last inserted ID value.
- */
- public function lastInsertId($sequence = null)
- {
- return !$sequence ? parent::lastInsertId() : parent::lastInsertId($sequence);
- }
+ /**
+ * Returns value of the last inserted ID.
+ *
+ * SQLSRV driver implements [[PDO::lastInsertId()]] method but with a single peculiarity:
+ * when `$sequence` value is a null or an empty string it returns an empty string.
+ * But when parameter is not specified it works as expected and returns actual
+ * last inserted ID (like the other PDO drivers).
+ * @param string|null $sequence the sequence name. Defaults to null.
+ * @return integer last inserted ID value.
+ */
+ public function lastInsertId($sequence = null)
+ {
+ return !$sequence ? parent::lastInsertId() : parent::lastInsertId($sequence);
+ }
}
diff --git a/framework/db/mssql/TableSchema.php b/framework/db/mssql/TableSchema.php
index 67ad85c1c97..6c0d330342b 100644
--- a/framework/db/mssql/TableSchema.php
+++ b/framework/db/mssql/TableSchema.php
@@ -15,9 +15,9 @@
*/
class TableSchema extends \yii\db\TableSchema
{
- /**
- * @var string name of the catalog (database) that this table belongs to.
- * Defaults to null, meaning no catalog (or the current database).
- */
- public $catalogName;
+ /**
+ * @var string name of the catalog (database) that this table belongs to.
+ * Defaults to null, meaning no catalog (or the current database).
+ */
+ public $catalogName;
}
diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php
index c8e8bb7dcce..16a12dc9308 100644
--- a/framework/db/mysql/QueryBuilder.php
+++ b/framework/db/mysql/QueryBuilder.php
@@ -18,147 +18,148 @@
*/
class QueryBuilder extends \yii\db\QueryBuilder
{
- /**
- * @var array mapping from abstract column types (keys) to physical column types (values).
- */
- public $typeMap = [
- Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
- Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
- Schema::TYPE_STRING => 'varchar(255)',
- Schema::TYPE_TEXT => 'text',
- Schema::TYPE_SMALLINT => 'smallint(6)',
- Schema::TYPE_INTEGER => 'int(11)',
- Schema::TYPE_BIGINT => 'bigint(20)',
- Schema::TYPE_FLOAT => 'float',
- Schema::TYPE_DECIMAL => 'decimal(10,0)',
- Schema::TYPE_DATETIME => 'datetime',
- Schema::TYPE_TIMESTAMP => 'timestamp',
- Schema::TYPE_TIME => 'time',
- Schema::TYPE_DATE => 'date',
- Schema::TYPE_BINARY => 'blob',
- Schema::TYPE_BOOLEAN => 'tinyint(1)',
- Schema::TYPE_MONEY => 'decimal(19,4)',
- ];
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = [
+ Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint(6)',
+ Schema::TYPE_INTEGER => 'int(11)',
+ Schema::TYPE_BIGINT => 'bigint(20)',
+ Schema::TYPE_FLOAT => 'float',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'blob',
+ Schema::TYPE_BOOLEAN => 'tinyint(1)',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ ];
- /**
- * Builds a SQL statement for renaming a column.
- * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
- * @param string $oldName the old name of the column. The name will be properly quoted by the method.
- * @param string $newName the new name of the column. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB column.
- * @throws Exception
- */
- public function renameColumn($table, $oldName, $newName)
- {
- $quotedTable = $this->db->quoteTableName($table);
- $row = $this->db->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryOne();
- if ($row === false) {
- throw new Exception("Unable to find column '$oldName' in table '$table'.");
- }
- if (isset($row['Create Table'])) {
- $sql = $row['Create Table'];
- } else {
- $row = array_values($row);
- $sql = $row[1];
- }
- if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) {
- foreach ($matches[1] as $i => $c) {
- if ($c === $oldName) {
- return "ALTER TABLE $quotedTable CHANGE "
- . $this->db->quoteColumnName($oldName) . ' '
- . $this->db->quoteColumnName($newName) . ' '
- . $matches[2][$i];
- }
- }
- }
- // try to give back a SQL anyway
- return "ALTER TABLE $quotedTable CHANGE "
- . $this->db->quoteColumnName($oldName) . ' '
- . $this->db->quoteColumnName($newName);
- }
+ /**
+ * Builds a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB column.
+ * @throws Exception
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ $quotedTable = $this->db->quoteTableName($table);
+ $row = $this->db->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryOne();
+ if ($row === false) {
+ throw new Exception("Unable to find column '$oldName' in table '$table'.");
+ }
+ if (isset($row['Create Table'])) {
+ $sql = $row['Create Table'];
+ } else {
+ $row = array_values($row);
+ $sql = $row[1];
+ }
+ if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) {
+ foreach ($matches[1] as $i => $c) {
+ if ($c === $oldName) {
+ return "ALTER TABLE $quotedTable CHANGE "
+ . $this->db->quoteColumnName($oldName) . ' '
+ . $this->db->quoteColumnName($newName) . ' '
+ . $matches[2][$i];
+ }
+ }
+ }
+ // try to give back a SQL anyway
+ return "ALTER TABLE $quotedTable CHANGE "
+ . $this->db->quoteColumnName($oldName) . ' '
+ . $this->db->quoteColumnName($newName);
+ }
- /**
- * Builds a SQL statement for dropping a foreign key constraint.
- * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping a foreign key constraint.
- */
- public function dropForeignKey($name, $table)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table)
- . ' DROP FOREIGN KEY ' . $this->db->quoteColumnName($name);
- }
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a foreign key constraint.
+ */
+ public function dropForeignKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' DROP FOREIGN KEY ' . $this->db->quoteColumnName($name);
+ }
- /**
- * Builds a SQL statement for removing a primary key constraint to an existing table.
- * @param string $name the name of the primary key constraint to be removed.
- * @param string $table the table that the primary key constraint will be removed from.
- * @return string the SQL statement for removing a primary key constraint from an existing table.
- */
- public function dropPrimaryKey($name, $table)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' DROP PRIMARY KEY';
- }
+ /**
+ * Builds a SQL statement for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return string the SQL statement for removing a primary key constraint from an existing table.
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' DROP PRIMARY KEY';
+ }
- /**
- * Creates a SQL statement for resetting the sequence value of a table's primary key.
- * The sequence will be reset such that the primary key of the next new row inserted
- * will have the specified value or 1.
- * @param string $tableName the name of the table whose primary key sequence will be reset
- * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
- * the next new row's primary key will have a value 1.
- * @return string the SQL statement for resetting sequence
- * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
- */
- public function resetSequence($tableName, $value = null)
- {
- $table = $this->db->getTableSchema($tableName);
- if ($table !== null && $table->sequenceName !== null) {
- $tableName = $this->db->quoteTableName($tableName);
- if ($value === null) {
- $key = reset($table->primaryKey);
- $value = $this->db->createCommand("SELECT MAX(`$key`) FROM $tableName")->queryScalar() + 1;
- } else {
- $value = (int)$value;
- }
- return "ALTER TABLE $tableName AUTO_INCREMENT=$value";
- } elseif ($table === null) {
- throw new InvalidParamException("Table not found: $tableName");
- } else {
- throw new InvalidParamException("There is no sequence associated with table '$tableName'.");
- }
- }
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $tableName the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function resetSequence($tableName, $value = null)
+ {
+ $table = $this->db->getTableSchema($tableName);
+ if ($table !== null && $table->sequenceName !== null) {
+ $tableName = $this->db->quoteTableName($tableName);
+ if ($value === null) {
+ $key = reset($table->primaryKey);
+ $value = $this->db->createCommand("SELECT MAX(`$key`) FROM $tableName")->queryScalar() + 1;
+ } else {
+ $value = (int) $value;
+ }
- /**
- * Builds a SQL statement for enabling or disabling integrity check.
- * @param boolean $check whether to turn on or off the integrity check.
- * @param string $table the table name. Meaningless for MySQL.
- * @param string $schema the schema of the tables. Meaningless for MySQL.
- * @return string the SQL statement for checking integrity
- */
- public function checkIntegrity($check = true, $schema = '', $table = '')
- {
- return 'SET FOREIGN_KEY_CHECKS = ' . ($check ? 1 : 0);
- }
+ return "ALTER TABLE $tableName AUTO_INCREMENT=$value";
+ } elseif ($table === null) {
+ throw new InvalidParamException("Table not found: $tableName");
+ } else {
+ throw new InvalidParamException("There is no sequence associated with table '$tableName'.");
+ }
+ }
- /**
- * @inheritdoc
- */
- public function buildLimit($limit, $offset)
- {
- $sql = '';
- if ($this->hasLimit($limit)) {
- $sql = 'LIMIT ' . $limit;
- if ($this->hasOffset($offset)) {
- $sql .= ' OFFSET ' . $offset;
- }
- } elseif ($this->hasOffset($offset)) {
- // limit is not optional in MySQL
- // http://stackoverflow.com/a/271650/1106908
- // http://dev.mysql.com/doc/refman/5.0/en/select.html#idm47619502796240
- $sql = "LIMIT $offset, 18446744073709551615"; // 2^64-1
- }
+ /**
+ * Builds a SQL statement for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $table the table name. Meaningless for MySQL.
+ * @param string $schema the schema of the tables. Meaningless for MySQL.
+ * @return string the SQL statement for checking integrity
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ return 'SET FOREIGN_KEY_CHECKS = ' . ($check ? 1 : 0);
+ }
- return $sql;
- }
+ /**
+ * @inheritdoc
+ */
+ public function buildLimit($limit, $offset)
+ {
+ $sql = '';
+ if ($this->hasLimit($limit)) {
+ $sql = 'LIMIT ' . $limit;
+ if ($this->hasOffset($offset)) {
+ $sql .= ' OFFSET ' . $offset;
+ }
+ } elseif ($this->hasOffset($offset)) {
+ // limit is not optional in MySQL
+ // http://stackoverflow.com/a/271650/1106908
+ // http://dev.mysql.com/doc/refman/5.0/en/select.html#idm47619502796240
+ $sql = "LIMIT $offset, 18446744073709551615"; // 2^64-1
+ }
+
+ return $sql;
+ }
}
diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php
index 38f999a084c..0c19c66fc73 100644
--- a/framework/db/mysql/Schema.php
+++ b/framework/db/mysql/Schema.php
@@ -18,275 +18,279 @@
*/
class Schema extends \yii\db\Schema
{
- /**
- * @var array mapping from physical column types (keys) to abstract column types (values)
- */
- public $typeMap = [
- 'tinyint' => self::TYPE_SMALLINT,
- 'bit' => self::TYPE_SMALLINT,
- 'smallint' => self::TYPE_SMALLINT,
- 'mediumint' => self::TYPE_INTEGER,
- 'int' => self::TYPE_INTEGER,
- 'integer' => self::TYPE_INTEGER,
- 'bigint' => self::TYPE_BIGINT,
- 'float' => self::TYPE_FLOAT,
- 'double' => self::TYPE_FLOAT,
- 'real' => self::TYPE_FLOAT,
- 'decimal' => self::TYPE_DECIMAL,
- 'numeric' => self::TYPE_DECIMAL,
- 'tinytext' => self::TYPE_TEXT,
- 'mediumtext' => self::TYPE_TEXT,
- 'longtext' => self::TYPE_TEXT,
- 'text' => self::TYPE_TEXT,
- 'varchar' => self::TYPE_STRING,
- 'string' => self::TYPE_STRING,
- 'char' => self::TYPE_STRING,
- 'datetime' => self::TYPE_DATETIME,
- 'year' => self::TYPE_DATE,
- 'date' => self::TYPE_DATE,
- 'time' => self::TYPE_TIME,
- 'timestamp' => self::TYPE_TIMESTAMP,
- 'enum' => self::TYPE_STRING,
- ];
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ */
+ public $typeMap = [
+ 'tinyint' => self::TYPE_SMALLINT,
+ 'bit' => self::TYPE_SMALLINT,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'mediumint' => self::TYPE_INTEGER,
+ 'int' => self::TYPE_INTEGER,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'float' => self::TYPE_FLOAT,
+ 'double' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'tinytext' => self::TYPE_TEXT,
+ 'mediumtext' => self::TYPE_TEXT,
+ 'longtext' => self::TYPE_TEXT,
+ 'text' => self::TYPE_TEXT,
+ 'varchar' => self::TYPE_STRING,
+ 'string' => self::TYPE_STRING,
+ 'char' => self::TYPE_STRING,
+ 'datetime' => self::TYPE_DATETIME,
+ 'year' => self::TYPE_DATE,
+ 'date' => self::TYPE_DATE,
+ 'time' => self::TYPE_TIME,
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'enum' => self::TYPE_STRING,
+ ];
- /**
- * Quotes a table name for use in a query.
- * A simple table name has no schema prefix.
- * @param string $name table name
- * @return string the properly quoted table name
- */
- public function quoteSimpleTableName($name)
- {
- return strpos($name, "`") !== false ? $name : "`" . $name . "`";
- }
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, "`") !== false ? $name : "`" . $name . "`";
+ }
- /**
- * Quotes a column name for use in a query.
- * A simple column name has no prefix.
- * @param string $name column name
- * @return string the properly quoted column name
- */
- public function quoteSimpleColumnName($name)
- {
- return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
- }
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
+ }
- /**
- * Creates a query builder for the MySQL database.
- * @return QueryBuilder query builder instance
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
+ /**
+ * Creates a query builder for the MySQL database.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
- /**
- * Loads the metadata for the specified table.
- * @param string $name table name
- * @return TableSchema driver dependent table metadata. Null if the table does not exist.
- */
- protected function loadTableSchema($name)
- {
- $table = new TableSchema;
- $this->resolveTableNames($table, $name);
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema driver dependent table metadata. Null if the table does not exist.
+ */
+ protected function loadTableSchema($name)
+ {
+ $table = new TableSchema;
+ $this->resolveTableNames($table, $name);
- if ($this->findColumns($table)) {
- $this->findConstraints($table);
- return $table;
- } else {
- return null;
- }
- }
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
- /**
- * Resolves the table name and schema name (if any).
- * @param TableSchema $table the table metadata object
- * @param string $name the table name
- */
- protected function resolveTableNames($table, $name)
- {
- $parts = explode('.', str_replace('`', '', $name));
- if (isset($parts[1])) {
- $table->schemaName = $parts[0];
- $table->name = $parts[1];
- $table->fullName = $table->schemaName . '.' . $table->name;
- } else {
- $table->fullName = $table->name = $parts[0];
- }
- }
+ return $table;
+ } else {
+ return null;
+ }
+ }
- /**
- * Loads the column information into a [[ColumnSchema]] object.
- * @param array $info column information
- * @return ColumnSchema the column schema object
- */
- protected function loadColumnSchema($info)
- {
- $column = new ColumnSchema;
+ /**
+ * Resolves the table name and schema name (if any).
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace('`', '', $name));
+ if (isset($parts[1])) {
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ $table->fullName = $table->schemaName . '.' . $table->name;
+ } else {
+ $table->fullName = $table->name = $parts[0];
+ }
+ }
- $column->name = $info['Field'];
- $column->allowNull = $info['Null'] === 'YES';
- $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
- $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
- $column->comment = $info['Comment'];
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema;
+ $column->name = $info['Field'];
+ $column->allowNull = $info['Null'] === 'YES';
+ $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
+ $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
+ $column->comment = $info['Comment'];
- $column->dbType = $info['Type'];
- $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
+ $column->dbType = $info['Type'];
+ $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
- $column->type = self::TYPE_STRING;
- if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
- $type = $matches[1];
- if (isset($this->typeMap[$type])) {
- $column->type = $this->typeMap[$type];
- }
- if (!empty($matches[2])) {
- if ($type === 'enum') {
- $values = explode(',', $matches[2]);
- foreach ($values as $i => $value) {
- $values[$i] = trim($value, "'");
- }
- $column->enumValues = $values;
- } else {
- $values = explode(',', $matches[2]);
- $column->size = $column->precision = (int)$values[0];
- if (isset($values[1])) {
- $column->scale = (int)$values[1];
- }
- if ($column->size === 1 && $type === 'bit') {
- $column->type = 'boolean';
- } elseif ($type === 'bit') {
- if ($column->size > 32) {
- $column->type = 'bigint';
- } elseif ($column->size === 32) {
- $column->type = 'integer';
- }
- }
- }
- }
- }
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = $matches[1];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+ if (!empty($matches[2])) {
+ if ($type === 'enum') {
+ $values = explode(',', $matches[2]);
+ foreach ($values as $i => $value) {
+ $values[$i] = trim($value, "'");
+ }
+ $column->enumValues = $values;
+ } else {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int) $values[0];
+ if (isset($values[1])) {
+ $column->scale = (int) $values[1];
+ }
+ if ($column->size === 1 && $type === 'bit') {
+ $column->type = 'boolean';
+ } elseif ($type === 'bit') {
+ if ($column->size > 32) {
+ $column->type = 'bigint';
+ } elseif ($column->size === 32) {
+ $column->type = 'integer';
+ }
+ }
+ }
+ }
+ }
- $column->phpType = $this->getColumnPhpType($column);
+ $column->phpType = $this->getColumnPhpType($column);
- if ($column->type !== 'timestamp' || $info['Default'] !== 'CURRENT_TIMESTAMP') {
- $column->defaultValue = $column->typecast($info['Default']);
- }
+ if ($column->type !== 'timestamp' || $info['Default'] !== 'CURRENT_TIMESTAMP') {
+ $column->defaultValue = $column->typecast($info['Default']);
+ }
- return $column;
- }
+ return $column;
+ }
- /**
- * Collects the metadata of table columns.
- * @param TableSchema $table the table metadata
- * @return boolean whether the table exists in the database
- * @throws \Exception if DB query fails
- */
- protected function findColumns($table)
- {
- $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
- try {
- $columns = $this->db->createCommand($sql)->queryAll();
- } catch (\Exception $e) {
- $previous = $e->getPrevious();
- if ($previous instanceof \PDOException && $previous->getCode() == '42S02') {
- // table does not exist
- return false;
- }
- throw $e;
- }
- foreach ($columns as $info) {
- $column = $this->loadColumnSchema($info);
- $table->columns[$column->name] = $column;
- if ($column->isPrimaryKey) {
- $table->primaryKey[] = $column->name;
- if ($column->autoIncrement) {
- $table->sequenceName = '';
- }
- }
- }
- return true;
- }
+ /**
+ * Collects the metadata of table columns.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ * @throws \Exception if DB query fails
+ */
+ protected function findColumns($table)
+ {
+ $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
+ try {
+ $columns = $this->db->createCommand($sql)->queryAll();
+ } catch (\Exception $e) {
+ $previous = $e->getPrevious();
+ if ($previous instanceof \PDOException && $previous->getCode() == '42S02') {
+ // table does not exist
+ return false;
+ }
+ throw $e;
+ }
+ foreach ($columns as $info) {
+ $column = $this->loadColumnSchema($info);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey) {
+ $table->primaryKey[] = $column->name;
+ if ($column->autoIncrement) {
+ $table->sequenceName = '';
+ }
+ }
+ }
- /**
- * Gets the CREATE TABLE sql string.
- * @param TableSchema $table the table metadata
- * @return string $sql the result of 'SHOW CREATE TABLE'
- */
- protected function getCreateTableSql($table)
- {
- $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteSimpleTableName($table->name))->queryOne();
- if (isset($row['Create Table'])) {
- $sql = $row['Create Table'];
- } else {
- $row = array_values($row);
- $sql = $row[1];
- }
- return $sql;
- }
+ return true;
+ }
- /**
- * Collects the foreign key column details for the given table.
- * @param TableSchema $table the table metadata
- */
- protected function findConstraints($table)
- {
- $sql = $this->getCreateTableSql($table);
+ /**
+ * Gets the CREATE TABLE sql string.
+ * @param TableSchema $table the table metadata
+ * @return string $sql the result of 'SHOW CREATE TABLE'
+ */
+ protected function getCreateTableSql($table)
+ {
+ $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteSimpleTableName($table->name))->queryOne();
+ if (isset($row['Create Table'])) {
+ $sql = $row['Create Table'];
+ } else {
+ $row = array_values($row);
+ $sql = $row[1];
+ }
- $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
- if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $match) {
- $fks = array_map('trim', explode(',', str_replace('`', '', $match[1])));
- $pks = array_map('trim', explode(',', str_replace('`', '', $match[3])));
- $constraint = [str_replace('`', '', $match[2])];
- foreach ($fks as $k => $name) {
- $constraint[$name] = $pks[$k];
- }
- $table->foreignKeys[] = $constraint;
- }
- }
- }
+ return $sql;
+ }
- /**
- * Returns all unique indexes for the given table.
- * Each array element is of the following structure:
- *
- * ~~~
- * [
- * 'IndexName1' => ['col1' [, ...]],
- * 'IndexName2' => ['col2' [, ...]],
- * ]
- * ~~~
- *
- * @param TableSchema $table the table metadata
- * @return array all unique indexes for the given table.
- */
- public function findUniqueIndexes($table)
- {
- $sql = $this->getCreateTableSql($table);
- $uniqueIndexes = [];
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+ $sql = $this->getCreateTableSql($table);
- $regexp = '/UNIQUE KEY\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
- if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $match) {
- $indexName = str_replace('`', '', $match[1]);
- $indexColumns = array_map('trim', explode(',', str_replace('`', '', $match[2])));
- $uniqueIndexes[$indexName] = $indexColumns;
- }
- }
- return $uniqueIndexes;
- }
+ $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
+ if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $fks = array_map('trim', explode(',', str_replace('`', '', $match[1])));
+ $pks = array_map('trim', explode(',', str_replace('`', '', $match[3])));
+ $constraint = [str_replace('`', '', $match[2])];
+ foreach ($fks as $k => $name) {
+ $constraint[$name] = $pks[$k];
+ }
+ $table->foreignKeys[] = $constraint;
+ }
+ }
+ }
- /**
- * Returns all table names in the database.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @return array all table names in the database. The names have NO schema name prefix.
- */
- protected function findTableNames($schema = '')
- {
- $sql = 'SHOW TABLES';
- if ($schema !== '') {
- $sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
- }
- return $this->db->createCommand($sql)->queryColumn();
- }
+ /**
+ * Returns all unique indexes for the given table.
+ * Each array element is of the following structure:
+ *
+ * ~~~
+ * [
+ * 'IndexName1' => ['col1' [, ...]],
+ * 'IndexName2' => ['col2' [, ...]],
+ * ]
+ * ~~~
+ *
+ * @param TableSchema $table the table metadata
+ * @return array all unique indexes for the given table.
+ */
+ public function findUniqueIndexes($table)
+ {
+ $sql = $this->getCreateTableSql($table);
+ $uniqueIndexes = [];
+
+ $regexp = '/UNIQUE KEY\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
+ if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $indexName = str_replace('`', '', $match[1]);
+ $indexColumns = array_map('trim', explode(',', str_replace('`', '', $match[2])));
+ $uniqueIndexes[$indexName] = $indexColumns;
+ }
+ }
+
+ return $uniqueIndexes;
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ $sql = 'SHOW TABLES';
+ if ($schema !== '') {
+ $sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
+ }
+
+ return $this->db->createCommand($sql)->queryColumn();
+ }
}
diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php
index 2bec96ce907..1dc7147055f 100644
--- a/framework/db/oci/QueryBuilder.php
+++ b/framework/db/oci/QueryBuilder.php
@@ -18,118 +18,120 @@
class QueryBuilder extends \yii\db\QueryBuilder
{
- private $sql;
-
- public function build($query)
- {
- $params = $query->params;
- $clauses = [
- $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),
- $this->buildHaving($query->having, $params),
- $this->buildOrderBy($query->orderBy),
- ];
- $this->sql = implode($this->separator, array_filter($clauses));
-
- $this->sql = $this->buildLimit($query->limit, $query->offset);
-
- $unions = $this->buildUnion($query->union, $params);
- if ($unions !== '') {
- $this->sql .= $this->separator . $unions;
- }
-
- return [$this->sql, $params];
- }
-
- public function buildLimit($limit, $offset)
- {
- $filters = [];
- if ($this->hasOffset($offset) > 0) {
- $filters[] = 'rowNumId > ' . $offset;
- }
-
- if ($this->hasLimit($limit)) {
- $filters[] = 'rownum <= ' . $limit;
- }
-
- if (!empty($filters)) {
- $filter = implode(' and ', $filters);
- return <<params;
+ $clauses = [
+ $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),
+ $this->buildHaving($query->having, $params),
+ $this->buildOrderBy($query->orderBy),
+ ];
+ $this->sql = implode($this->separator, array_filter($clauses));
+
+ $this->sql = $this->buildLimit($query->limit, $query->offset);
+
+ $unions = $this->buildUnion($query->union, $params);
+ if ($unions !== '') {
+ $this->sql .= $this->separator . $unions;
+ }
+
+ return [$this->sql, $params];
+ }
+
+ public function buildLimit($limit, $offset)
+ {
+ $filters = [];
+ if ($this->hasOffset($offset) > 0) {
+ $filters[] = 'rowNumId > ' . $offset;
+ }
+
+ if ($this->hasLimit($limit)) {
+ $filters[] = 'rownum <= ' . $limit;
+ }
+
+ if (!empty($filters)) {
+ $filter = implode(' and ', $filters);
+
+ return <<sql}),
- PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL)
+ PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL)
SELECT *
FROM PAGINATION
WHERE $filter
EOD;
- } else {
- return $this->sql;
- }
- }
-
-
- /**
- * Builds a SQL statement for renaming a DB table.
- *
- * @param string $table the table to be renamed. The name will be properly quoted by the method.
- * @param string $newName the new table name. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB table.
- */
- public function renameTable($table, $newName)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' RENAME TO ' . $this->db->quoteTableName($newName);
- }
-
- /**
- * Builds a SQL statement for changing the definition of a column.
- *
- * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
- * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
- * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
- * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
- * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
- * @return string the SQL statement for changing the definition of a column.
- */
- public function alterColumn($table, $column, $type)
- {
- $type = $this->getColumnType($type);
- return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' MODIFY ' . $this->db->quoteColumnName($column) . ' ' . $this->getColumnType($type);
- }
-
- /**
- * Builds a SQL statement for dropping an index.
- *
- * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping an index.
- */
- public function dropIndex($name, $table)
- {
- return 'DROP INDEX ' . $this->db->quoteTableName($name);
- }
-
- /**
- * @inheritdoc
- */
- public function resetSequence($table, $value = null)
- {
- $tableSchema = $this->db->getTableSchema($table);
- if ($tableSchema === null) {
- throw new InvalidParamException("Unknown table: $table");
- }
- if ($tableSchema->sequenceName === null) {
- return '';
- }
-
- if ($value !== null) {
- $value = (int)$value;
- } else {
- $value = (int)$this->db->createCommand("SELECT MAX(\"{$tableSchema->primaryKey}\") FROM \"{$tableSchema->name}\"")->queryScalar();
- $value++;
- }
- return "DROP SEQUENCE \"{$tableSchema->name}_SEQ\";"
- . "CREATE SEQUENCE \"{$tableSchema->name}_SEQ\" START WITH {$value} INCREMENT BY 1 NOMAXVALUE NOCACHE";
- }
+ } else {
+ return $this->sql;
+ }
+ }
+
+ /**
+ * Builds a SQL statement for renaming a DB table.
+ *
+ * @param string $table the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB table.
+ */
+ public function renameTable($table, $newName)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' RENAME TO ' . $this->db->quoteTableName($newName);
+ }
+
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ *
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ $type = $this->getColumnType($type);
+
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' MODIFY ' . $this->db->quoteColumnName($column) . ' ' . $this->getColumnType($type);
+ }
+
+ /**
+ * Builds a SQL statement for dropping an index.
+ *
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping an index.
+ */
+ public function dropIndex($name, $table)
+ {
+ return 'DROP INDEX ' . $this->db->quoteTableName($name);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resetSequence($table, $value = null)
+ {
+ $tableSchema = $this->db->getTableSchema($table);
+ if ($tableSchema === null) {
+ throw new InvalidParamException("Unknown table: $table");
+ }
+ if ($tableSchema->sequenceName === null) {
+ return '';
+ }
+
+ if ($value !== null) {
+ $value = (int) $value;
+ } else {
+ $value = (int) $this->db->createCommand("SELECT MAX(\"{$tableSchema->primaryKey}\") FROM \"{$tableSchema->name}\"")->queryScalar();
+ $value++;
+ }
+
+ return "DROP SEQUENCE \"{$tableSchema->name}_SEQ\";"
+ . "CREATE SEQUENCE \"{$tableSchema->name}_SEQ\" START WITH {$value} INCREMENT BY 1 NOMAXVALUE NOCACHE";
+ }
}
diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php
index 1edbf6b6175..6fe42f85e97 100644
--- a/framework/db/oci/Schema.php
+++ b/framework/db/oci/Schema.php
@@ -20,96 +20,97 @@
*/
class Schema extends \yii\db\Schema
{
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->defaultSchema === null) {
- $this->defaultSchema = $this->db->username;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->defaultSchema === null) {
+ $this->defaultSchema = $this->db->username;
+ }
+ }
- /**
- * @inheritdoc
- */
- public function releaseSavepoint($name)
- {
- // does nothing as Oracle does not support this
- }
+ /**
+ * @inheritdoc
+ */
+ public function releaseSavepoint($name)
+ {
+ // does nothing as Oracle does not support this
+ }
- /**
- * @inheritdoc
- */
- public function quoteSimpleTableName($name)
- {
- return '"' . $name . '"';
- }
+ /**
+ * @inheritdoc
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return '"' . $name . '"';
+ }
- /**
- * @inheritdoc
- */
- public function quoteSimpleColumnName($name)
- {
- return '"' . $name . '"';
- }
+ /**
+ * @inheritdoc
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return '"' . $name . '"';
+ }
- /**
- * @inheritdoc
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
+ /**
+ * @inheritdoc
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
- /**
- * @inheritdoc
- */
- public function loadTableSchema($name)
- {
- $table = new TableSchema();
- $this->resolveTableNames($table, $name);
+ /**
+ * @inheritdoc
+ */
+ public function loadTableSchema($name)
+ {
+ $table = new TableSchema();
+ $this->resolveTableNames($table, $name);
- if ($this->findColumns($table)) {
- $this->findConstraints($table);
- return $table;
- } else {
- return null;
- }
- }
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
- /**
- * Resolves the table name and schema name (if any).
- *
- * @param TableSchema $table the table metadata object
- * @param string $name the table name
- */
- protected function resolveTableNames($table, $name)
- {
- $parts = explode('.', str_replace('"', '', $name));
- if (isset($parts[1])) {
- $table->schemaName = $parts[0];
- $table->name = $parts[1];
- } else {
- $table->schemaName = $this->defaultSchema;
- $table->name = $name;
- }
+ return $table;
+ } else {
+ return null;
+ }
+ }
- $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
- }
+ /**
+ * Resolves the table name and schema name (if any).
+ *
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace('"', '', $name));
+ if (isset($parts[1])) {
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ } else {
+ $table->schemaName = $this->defaultSchema;
+ $table->name = $name;
+ }
- /**
- * Collects the table column metadata.
- * @param TableSchema $table the table schema
- * @return boolean whether the table exists
- */
- protected function findColumns($table)
- {
- $schemaName = $table->schemaName;
- $tableName = $table->name;
+ $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
+ }
- $sql = <<schemaName;
+ $tableName = $table->name;
+
+ $sql = <<db->createCommand($sql)->queryAll();
- } catch (\Exception $e) {
- return false;
- }
+ try {
+ $columns = $this->db->createCommand($sql)->queryAll();
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ foreach ($columns as $column) {
+ $c = $this->createColumn($column);
+ $table->columns[$c->name] = $c;
+ if ($c->isPrimaryKey) {
+ $table->primaryKey[] = $c->name;
+ $table->sequenceName = '';
+ $c->autoIncrement = true;
+ }
+ }
- foreach ($columns as $column) {
- $c = $this->createColumn($column);
- $table->columns[$c->name] = $c;
- if ($c->isPrimaryKey) {
- $table->primaryKey[] = $c->name;
- $table->sequenceName = '';
- $c->autoIncrement = true;
- }
- }
- return true;
- }
+ return true;
+ }
- protected function createColumn($column)
- {
- $c = new ColumnSchema();
- $c->name = $column['COLUMN_NAME'];
- $c->allowNull = $column['NULLABLE'] === 'Y';
- $c->isPrimaryKey = strpos($column['KEY'], 'P') !== false;
- $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT'];
+ protected function createColumn($column)
+ {
+ $c = new ColumnSchema();
+ $c->name = $column['COLUMN_NAME'];
+ $c->allowNull = $column['NULLABLE'] === 'Y';
+ $c->isPrimaryKey = strpos($column['KEY'], 'P') !== false;
+ $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT'];
- $this->extractColumnType($c, $column['DATA_TYPE']);
- $this->extractColumnSize($c, $column['DATA_TYPE']);
+ $this->extractColumnType($c, $column['DATA_TYPE']);
+ $this->extractColumnSize($c, $column['DATA_TYPE']);
- if (stripos($column['DATA_DEFAULT'], 'timestamp') !== false) {
- $c->defaultValue = null;
- } else {
- $c->defaultValue = $c->typecast($column['DATA_DEFAULT']);
- }
+ if (stripos($column['DATA_DEFAULT'], 'timestamp') !== false) {
+ $c->defaultValue = null;
+ } else {
+ $c->defaultValue = $c->typecast($column['DATA_DEFAULT']);
+ }
- return $c;
- }
+ return $c;
+ }
- protected function findConstraints($table)
- {
- $sql = << 'P'
order by d.constraint_name, c.position
EOD;
- $command = $this->db->createCommand($sql);
- foreach ($command->queryAll() as $row) {
- if ($row['CONSTRAINT_TYPE'] === 'R') {
- $name = $row["COLUMN_NAME"];
- $table->foreignKeys[$name] = [$row["TABLE_REF"], $row["COLUMN_REF"]];
- }
- }
- }
+ $command = $this->db->createCommand($sql);
+ foreach ($command->queryAll() as $row) {
+ if ($row['CONSTRAINT_TYPE'] === 'R') {
+ $name = $row["COLUMN_NAME"];
+ $table->foreignKeys[$name] = [$row["TABLE_REF"], $row["COLUMN_REF"]];
+ }
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function findTableNames($schema = '')
- {
- if ($schema === '') {
- $sql = <<db->createCommand($sql);
- } else {
- $sql = <<db->createCommand($sql);
+ } else {
+ $sql = <<db->createCommand($sql);
- $command->bindParam(':schema', $schema);
- }
+ $command = $this->db->createCommand($sql);
+ $command->bindParam(':schema', $schema);
+ }
+
+ $rows = $command->queryAll();
+ $names = [];
+ foreach ($rows as $row) {
+ $names[] = $row['TABLE_NAME'];
+ }
- $rows = $command->queryAll();
- $names = [];
- foreach ($rows as $row) {
- $names[] = $row['TABLE_NAME'];
- }
- return $names;
- }
+ return $names;
+ }
- /**
- * Extracts the data types for the given column
- * @param ColumnSchema $column
- * @param string $dbType DB type
- */
- protected function extractColumnType($column, $dbType)
- {
- $column->dbType = $dbType;
+ /**
+ * Extracts the data types for the given column
+ * @param ColumnSchema $column
+ * @param string $dbType DB type
+ */
+ protected function extractColumnType($column, $dbType)
+ {
+ $column->dbType = $dbType;
- if (strpos($dbType, 'FLOAT') !== false) {
- $column->type = 'double';
- } elseif (strpos($dbType, 'NUMBER') !== false || strpos($dbType, 'INTEGER') !== false) {
- if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) {
- $values = explode(',', $matches[1]);
- if (isset($values[1]) && (((int)$values[1]) > 0)) {
- $column->type = 'double';
- } else {
- $column->type = 'integer';
- }
- } else {
- $column->type = 'double';
- }
- } else {
- $column->type = 'string';
- }
- }
+ if (strpos($dbType, 'FLOAT') !== false) {
+ $column->type = 'double';
+ } elseif (strpos($dbType, 'NUMBER') !== false || strpos($dbType, 'INTEGER') !== false) {
+ if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) {
+ $values = explode(',', $matches[1]);
+ if (isset($values[1]) && (((int) $values[1]) > 0)) {
+ $column->type = 'double';
+ } else {
+ $column->type = 'integer';
+ }
+ } else {
+ $column->type = 'double';
+ }
+ } else {
+ $column->type = 'string';
+ }
+ }
- /**
- * Extracts size, precision and scale information from column's DB type.
- * @param ColumnSchema $column
- * @param string $dbType the column's DB type
- */
- protected function extractColumnSize($column, $dbType)
- {
- if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) {
- $values = explode(',', $matches[1]);
- $column->size = $column->precision = (int)$values[0];
- if (isset($values[1])) {
- $column->scale = (int)$values[1];
- }
- }
- }
+ /**
+ * Extracts size, precision and scale information from column's DB type.
+ * @param ColumnSchema $column
+ * @param string $dbType the column's DB type
+ */
+ protected function extractColumnSize($column, $dbType)
+ {
+ if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) {
+ $values = explode(',', $matches[1]);
+ $column->size = $column->precision = (int) $values[0];
+ if (isset($values[1])) {
+ $column->scale = (int) $values[1];
+ }
+ }
+ }
}
diff --git a/framework/db/pgsql/QueryBuilder.php b/framework/db/pgsql/QueryBuilder.php
index 998e7469aeb..932653d8949 100644
--- a/framework/db/pgsql/QueryBuilder.php
+++ b/framework/db/pgsql/QueryBuilder.php
@@ -19,123 +19,125 @@
class QueryBuilder extends \yii\db\QueryBuilder
{
- /**
- * @var array mapping from abstract column types (keys) to physical column types (values).
- */
- public $typeMap = [
- Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY',
- Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY',
- Schema::TYPE_STRING => 'varchar(255)',
- Schema::TYPE_TEXT => 'text',
- Schema::TYPE_SMALLINT => 'smallint',
- Schema::TYPE_INTEGER => 'integer',
- Schema::TYPE_BIGINT => 'bigint',
- Schema::TYPE_FLOAT => 'double precision',
- Schema::TYPE_DECIMAL => 'numeric(10,0)',
- Schema::TYPE_DATETIME => 'timestamp',
- Schema::TYPE_TIMESTAMP => 'timestamp',
- Schema::TYPE_TIME => 'time',
- Schema::TYPE_DATE => 'date',
- Schema::TYPE_BINARY => 'bytea',
- Schema::TYPE_BOOLEAN => 'boolean',
- Schema::TYPE_MONEY => 'numeric(19,4)',
- ];
-
- /**
- * Builds a SQL statement for dropping an index.
- * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping an index.
- */
- public function dropIndex($name, $table)
- {
- return 'DROP INDEX ' . $this->db->quoteTableName($name);
- }
-
- /**
- * Builds a SQL statement for renaming a DB table.
- * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
- * @param string $newName the new table name. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB table.
- */
- public function renameTable($oldName, $newName)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName);
- }
-
- /**
- * Creates a SQL statement for resetting the sequence value of a table's primary key.
- * The sequence will be reset such that the primary key of the next new row inserted
- * will have the specified value or 1.
- * @param string $tableName the name of the table whose primary key sequence will be reset
- * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
- * the next new row's primary key will have a value 1.
- * @return string the SQL statement for resetting sequence
- * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
- */
- public function resetSequence($tableName, $value = null)
- {
- $table = $this->db->getTableSchema($tableName);
- if ($table !== null && $table->sequenceName !== null) {
- $sequence = '"' . $table->sequenceName . '"';
-
- if (strpos($sequence, '.') !== false) {
- $sequence = str_replace('.', '"."', $sequence);
- }
-
- $tableName = $this->db->quoteTableName($tableName);
- if ($value === null) {
- $key = reset($table->primaryKey);
- $value = "(SELECT COALESCE(MAX(\"{$key}\"),0) FROM {$tableName})+1";
- } else {
- $value = (int)$value;
- }
- return "SELECT SETVAL('$sequence',$value,false)";
- } elseif ($table === null) {
- throw new InvalidParamException("Table not found: $tableName");
- } else {
- throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
- }
- }
-
- /**
- * Builds a SQL statement for enabling or disabling integrity check.
- * @param boolean $check whether to turn on or off the integrity check.
- * @param string $schema the schema of the tables.
- * @param string $table the table name.
- * @return string the SQL statement for checking integrity
- */
- public function checkIntegrity($check = true, $schema = '', $table = '')
- {
- $enable = $check ? 'ENABLE' : 'DISABLE';
- $schema = $schema ? $schema : $this->db->schema->defaultSchema;
- $tableNames = $table ? [$table] : $this->db->schema->getTableNames($schema);
- $command = '';
-
- foreach ($tableNames as $tableName) {
- $tableName = '"' . $schema . '"."' . $tableName . '"';
- $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; ";
- }
-
- #enable to have ability to alter several tables
- $this->db->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
- return $command;
- }
-
- /**
- * Builds a SQL statement for changing the definition of a column.
- * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
- * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
- * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
- * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
- * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
- * will become 'varchar(255) not null'.
- * @return string the SQL statement for changing the definition of a column.
- */
- public function alterColumn($table, $column, $type)
- {
- return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
- . $this->db->quoteColumnName($column) . ' TYPE '
- . $this->getColumnType($type);
- }
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = [
+ Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'integer',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'double precision',
+ Schema::TYPE_DECIMAL => 'numeric(10,0)',
+ Schema::TYPE_DATETIME => 'timestamp',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'bytea',
+ Schema::TYPE_BOOLEAN => 'boolean',
+ Schema::TYPE_MONEY => 'numeric(19,4)',
+ ];
+
+ /**
+ * Builds a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping an index.
+ */
+ public function dropIndex($name, $table)
+ {
+ return 'DROP INDEX ' . $this->db->quoteTableName($name);
+ }
+
+ /**
+ * Builds a SQL statement for renaming a DB table.
+ * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB table.
+ */
+ public function renameTable($oldName, $newName)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName);
+ }
+
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $tableName the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function resetSequence($tableName, $value = null)
+ {
+ $table = $this->db->getTableSchema($tableName);
+ if ($table !== null && $table->sequenceName !== null) {
+ $sequence = '"' . $table->sequenceName . '"';
+
+ if (strpos($sequence, '.') !== false) {
+ $sequence = str_replace('.', '"."', $sequence);
+ }
+
+ $tableName = $this->db->quoteTableName($tableName);
+ if ($value === null) {
+ $key = reset($table->primaryKey);
+ $value = "(SELECT COALESCE(MAX(\"{$key}\"),0) FROM {$tableName})+1";
+ } else {
+ $value = (int) $value;
+ }
+
+ return "SELECT SETVAL('$sequence',$value,false)";
+ } elseif ($table === null) {
+ throw new InvalidParamException("Table not found: $tableName");
+ } else {
+ throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
+ }
+ }
+
+ /**
+ * Builds a SQL statement for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema of the tables.
+ * @param string $table the table name.
+ * @return string the SQL statement for checking integrity
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ $enable = $check ? 'ENABLE' : 'DISABLE';
+ $schema = $schema ? $schema : $this->db->schema->defaultSchema;
+ $tableNames = $table ? [$table] : $this->db->schema->getTableNames($schema);
+ $command = '';
+
+ foreach ($tableNames as $tableName) {
+ $tableName = '"' . $schema . '"."' . $tableName . '"';
+ $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; ";
+ }
+
+ #enable to have ability to alter several tables
+ $this->db->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
+
+ return $command;
+ }
+
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
+ * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
+ * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
+ * will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
+ . $this->db->quoteColumnName($column) . ' TYPE '
+ . $this->getColumnType($type);
+ }
}
diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php
index b13fdf7a2ae..92c1a4af025 100644
--- a/framework/db/pgsql/Schema.php
+++ b/framework/db/pgsql/Schema.php
@@ -20,229 +20,232 @@
*/
class Schema extends \yii\db\Schema
{
- /**
- * @var string the default schema used for the current session.
- */
- public $defaultSchema = 'public';
- /**
- * @var array mapping from physical column types (keys) to abstract
- * column types (values)
- */
- public $typeMap = [
- 'abstime' => self::TYPE_TIMESTAMP,
- 'bit' => self::TYPE_STRING,
- 'bool' => self::TYPE_BOOLEAN,
- 'boolean' => self::TYPE_BOOLEAN,
- 'box' => self::TYPE_STRING,
- 'character' => self::TYPE_STRING,
- 'bytea' => self::TYPE_BINARY,
- 'char' => self::TYPE_STRING,
- 'cidr' => self::TYPE_STRING,
- 'circle' => self::TYPE_STRING,
- 'date' => self::TYPE_DATE,
- 'real' => self::TYPE_FLOAT,
- 'decimal' => self::TYPE_DECIMAL,
- 'double precision' => self::TYPE_DECIMAL,
- 'inet' => self::TYPE_STRING,
- 'smallint' => self::TYPE_SMALLINT,
- 'int4' => self::TYPE_INTEGER,
- 'int8' => self::TYPE_BIGINT,
- 'integer' => self::TYPE_INTEGER,
- 'bigint' => self::TYPE_BIGINT,
- 'interval' => self::TYPE_STRING,
- 'json' => self::TYPE_STRING,
- 'line' => self::TYPE_STRING,
- 'macaddr' => self::TYPE_STRING,
- 'money' => self::TYPE_MONEY,
- 'name' => self::TYPE_STRING,
- 'numeric' => self::TYPE_STRING,
- 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
- 'path' => self::TYPE_STRING,
- 'point' => self::TYPE_STRING,
- 'polygon' => self::TYPE_STRING,
- 'text' => self::TYPE_TEXT,
- 'time without time zone' => self::TYPE_TIME,
- 'timestamp without time zone' => self::TYPE_TIMESTAMP,
- 'timestamp with time zone' => self::TYPE_TIMESTAMP,
- 'time with time zone' => self::TYPE_TIMESTAMP,
- 'unknown' => self::TYPE_STRING,
- 'uuid' => self::TYPE_STRING,
- 'bit varying' => self::TYPE_STRING,
- 'character varying' => self::TYPE_STRING,
- 'xml' => self::TYPE_STRING
- ];
-
- /**
- * Creates a query builder for the PostgreSQL database.
- * @return QueryBuilder query builder instance
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
-
- /**
- * Resolves the table name and schema name (if any).
- * @param TableSchema $table the table metadata object
- * @param string $name the table name
- */
- protected function resolveTableNames($table, $name)
- {
- $parts = explode('.', str_replace('"', '', $name));
-
- if (isset($parts[1])) {
- $table->schemaName = $parts[0];
- $table->name = $parts[1];
- } else {
- $table->schemaName = $this->defaultSchema;
- $table->name = $name;
- }
-
- $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
- }
-
- /**
- * Quotes a table name for use in a query.
- * A simple table name has no schema prefix.
- * @param string $name table name
- * @return string the properly quoted table name
- */
- public function quoteSimpleTableName($name)
- {
- return strpos($name, '"') !== false ? $name : '"' . $name . '"';
- }
-
- /**
- * Loads the metadata for the specified table.
- * @param string $name table name
- * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
- */
- public function loadTableSchema($name)
- {
- $table = new TableSchema();
- $this->resolveTableNames($table, $name);
- if ($this->findColumns($table)) {
- $this->findConstraints($table);
- return $table;
- } else {
- return null;
- }
- }
-
- /**
- * Determines the PDO type for the given PHP data value.
- * @param mixed $data the data whose PDO type is to be determined
- * @return integer the PDO type
- * @see http://www.php.net/manual/en/pdo.constants.php
- */
- public function getPdoType($data)
- {
- // php type => PDO type
- static $typeMap = [
- // https://github.com/yiisoft/yii2/issues/1115
- // Cast boolean to integer values to work around problems with PDO casting false to string '' https://bugs.php.net/bug.php?id=33876
- 'boolean' => \PDO::PARAM_INT,
- 'integer' => \PDO::PARAM_INT,
- 'string' => \PDO::PARAM_STR,
- 'resource' => \PDO::PARAM_LOB,
- 'NULL' => \PDO::PARAM_NULL,
- ];
- $type = gettype($data);
- return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
- }
-
- /**
- * Returns all table names in the database.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @return array all table names in the database. The names have NO schema name prefix.
- */
- protected function findTableNames($schema = '')
- {
- if ($schema === '') {
- $schema = $this->defaultSchema;
- }
- $sql = << self::TYPE_TIMESTAMP,
+ 'bit' => self::TYPE_STRING,
+ 'bool' => self::TYPE_BOOLEAN,
+ 'boolean' => self::TYPE_BOOLEAN,
+ 'box' => self::TYPE_STRING,
+ 'character' => self::TYPE_STRING,
+ 'bytea' => self::TYPE_BINARY,
+ 'char' => self::TYPE_STRING,
+ 'cidr' => self::TYPE_STRING,
+ 'circle' => self::TYPE_STRING,
+ 'date' => self::TYPE_DATE,
+ 'real' => self::TYPE_FLOAT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'double precision' => self::TYPE_DECIMAL,
+ 'inet' => self::TYPE_STRING,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'int4' => self::TYPE_INTEGER,
+ 'int8' => self::TYPE_BIGINT,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'interval' => self::TYPE_STRING,
+ 'json' => self::TYPE_STRING,
+ 'line' => self::TYPE_STRING,
+ 'macaddr' => self::TYPE_STRING,
+ 'money' => self::TYPE_MONEY,
+ 'name' => self::TYPE_STRING,
+ 'numeric' => self::TYPE_STRING,
+ 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
+ 'path' => self::TYPE_STRING,
+ 'point' => self::TYPE_STRING,
+ 'polygon' => self::TYPE_STRING,
+ 'text' => self::TYPE_TEXT,
+ 'time without time zone' => self::TYPE_TIME,
+ 'timestamp without time zone' => self::TYPE_TIMESTAMP,
+ 'timestamp with time zone' => self::TYPE_TIMESTAMP,
+ 'time with time zone' => self::TYPE_TIMESTAMP,
+ 'unknown' => self::TYPE_STRING,
+ 'uuid' => self::TYPE_STRING,
+ 'bit varying' => self::TYPE_STRING,
+ 'character varying' => self::TYPE_STRING,
+ 'xml' => self::TYPE_STRING
+ ];
+
+ /**
+ * Creates a query builder for the PostgreSQL database.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Resolves the table name and schema name (if any).
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace('"', '', $name));
+
+ if (isset($parts[1])) {
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ } else {
+ $table->schemaName = $this->defaultSchema;
+ $table->name = $name;
+ }
+
+ $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
+ }
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, '"') !== false ? $name : '"' . $name . '"';
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
+ */
+ public function loadTableSchema($name)
+ {
+ $table = new TableSchema();
+ $this->resolveTableNames($table, $name);
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
+
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Determines the PDO type for the given PHP data value.
+ * @param mixed $data the data whose PDO type is to be determined
+ * @return integer the PDO type
+ * @see http://www.php.net/manual/en/pdo.constants.php
+ */
+ public function getPdoType($data)
+ {
+ // php type => PDO type
+ static $typeMap = [
+ // https://github.com/yiisoft/yii2/issues/1115
+ // Cast boolean to integer values to work around problems with PDO casting false to string '' https://bugs.php.net/bug.php?id=33876
+ 'boolean' => \PDO::PARAM_INT,
+ 'integer' => \PDO::PARAM_INT,
+ 'string' => \PDO::PARAM_STR,
+ 'resource' => \PDO::PARAM_LOB,
+ 'NULL' => \PDO::PARAM_NULL,
+ ];
+ $type = gettype($data);
+
+ return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ if ($schema === '') {
+ $schema = $this->defaultSchema;
+ }
+ $sql = <<db->createCommand($sql);
- $command->bindParam(':schema', $schema);
- $rows = $command->queryAll();
- $names = [];
- foreach ($rows as $row) {
- $names[] = $row['table_name'];
- }
- return $names;
- }
-
- /**
- * Collects the foreign key column details for the given table.
- * @param TableSchema $table the table metadata
- */
- protected function findConstraints($table)
- {
-
- $tableName = $this->quoteValue($table->name);
- $tableSchema = $this->quoteValue($table->schemaName);
-
- //We need to extract the constraints de hard way since:
- //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us
-
- $sql = <<db->createCommand($sql);
+ $command->bindParam(':schema', $schema);
+ $rows = $command->queryAll();
+ $names = [];
+ foreach ($rows as $row) {
+ $names[] = $row['table_name'];
+ }
+
+ return $names;
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+
+ $tableName = $this->quoteValue($table->name);
+ $tableSchema = $this->quoteValue($table->schemaName);
+
+ //We need to extract the constraints de hard way since:
+ //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us
+
+ $sql = <<db->createCommand($sql)->queryAll();
- foreach ($constraints as $constraint) {
- $columns = explode(',', $constraint['columns']);
- $fcolumns = explode(',', $constraint['foreign_columns']);
- if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
- $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
- } else {
- $foreignTable = $constraint['foreign_table_name'];
- }
- $citem = [$foreignTable];
- foreach ($columns as $idx => $column) {
- $citem[$column] = $fcolumns[$idx];
- }
- $table->foreignKeys[] = $citem;
- }
- }
-
- /**
- * Gets information about given table unique indexes.
- * @param TableSchema $table the table metadata
- * @return array with index names, columns and if it is an expression tree
- */
- protected function getUniqueIndexInformation($table)
- {
- $tableName = $this->quoteValue($table->name);
- $tableSchema = $this->quoteValue($table->schemaName);
-
- $sql = <<db->createCommand($sql)->queryAll();
+ foreach ($constraints as $constraint) {
+ $columns = explode(',', $constraint['columns']);
+ $fcolumns = explode(',', $constraint['foreign_columns']);
+ if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
+ $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
+ } else {
+ $foreignTable = $constraint['foreign_table_name'];
+ }
+ $citem = [$foreignTable];
+ foreach ($columns as $idx => $column) {
+ $citem[$column] = $fcolumns[$idx];
+ }
+ $table->foreignKeys[] = $citem;
+ }
+ }
+
+ /**
+ * Gets information about given table unique indexes.
+ * @param TableSchema $table the table metadata
+ * @return array with index names, columns and if it is an expression tree
+ */
+ protected function getUniqueIndexInformation($table)
+ {
+ $tableName = $this->quoteValue($table->name);
+ $tableSchema = $this->quoteValue($table->schemaName);
+
+ $sql = <<db->createCommand($sql)->queryAll();
- }
-
- /**
- * Returns all unique indexes for the given table.
- * Each array element is of the following structure:
- *
- * ~~~
- * [
- * 'IndexName1' => ['col1' [, ...]],
- * 'IndexName2' => ['col2' [, ...]],
- * ]
- * ~~~
- *
- * @param TableSchema $table the table metadata
- * @return array all unique indexes for the given table.
- */
- public function findUniqueIndexes($table)
- {
- $indexes = $this->getUniqueIndexInformation($table);
- $uniqueIndexes = [];
-
- foreach ($indexes as $index) {
- $indexName = $index['indexname'];
-
- if ($index['indexprs']) {
- // Index is an expression like "lower(colname::text)"
- $indexColumns = preg_replace("/.*\(([^\:]+).*/mi", "$1", $index['indexcolumns']);
- } else {
- $indexColumns = array_map('trim', explode(',', str_replace(['{', '}', '"', '\\'], '', $index['indexcolumns'])));
- }
-
- $uniqueIndexes[$indexName] = $indexColumns;
-
- }
- return $uniqueIndexes;
- }
-
- /**
- * Collects the metadata of table columns.
- * @param TableSchema $table the table metadata
- * @return boolean whether the table exists in the database
- */
- protected function findColumns($table)
- {
- $tableName = $this->db->quoteValue($table->name);
- $schemaName = $this->db->quoteValue($table->schemaName);
- $sql = <<db->createCommand($sql)->queryAll();
+ }
+
+ /**
+ * Returns all unique indexes for the given table.
+ * Each array element is of the following structure:
+ *
+ * ~~~
+ * [
+ * 'IndexName1' => ['col1' [, ...]],
+ * 'IndexName2' => ['col2' [, ...]],
+ * ]
+ * ~~~
+ *
+ * @param TableSchema $table the table metadata
+ * @return array all unique indexes for the given table.
+ */
+ public function findUniqueIndexes($table)
+ {
+ $indexes = $this->getUniqueIndexInformation($table);
+ $uniqueIndexes = [];
+
+ foreach ($indexes as $index) {
+ $indexName = $index['indexname'];
+
+ if ($index['indexprs']) {
+ // Index is an expression like "lower(colname::text)"
+ $indexColumns = preg_replace("/.*\(([^\:]+).*/mi", "$1", $index['indexcolumns']);
+ } else {
+ $indexColumns = array_map('trim', explode(',', str_replace(['{', '}', '"', '\\'], '', $index['indexcolumns'])));
+ }
+
+ $uniqueIndexes[$indexName] = $indexColumns;
+
+ }
+
+ return $uniqueIndexes;
+ }
+
+ /**
+ * Collects the metadata of table columns.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ $tableName = $this->db->quoteValue($table->name);
+ $schemaName = $this->db->quoteValue($table->schemaName);
+ $sql = <<> 16) & 65535
- END
- WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
- WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
- ELSE null
- END AS numeric_precision,
- CASE
- WHEN atttypid IN (21, 23, 20) THEN 0
- WHEN atttypid IN (1700) THEN
- CASE
- WHEN atttypmod = -1 THEN null
- ELSE (atttypmod - 4) & 65535
- END
- ELSE null
- END AS numeric_scale,
- CAST(
+ d.nspname AS table_schema,
+ c.relname AS table_name,
+ a.attname AS column_name,
+ t.typname AS data_type,
+ a.attlen AS character_maximum_length,
+ pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
+ a.atttypmod AS modifier,
+ a.attnotnull = false AS is_nullable,
+ CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
+ coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc,
+ array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values,
+ CASE atttypid
+ WHEN 21 /*int2*/ THEN 16
+ WHEN 23 /*int4*/ THEN 32
+ WHEN 20 /*int8*/ THEN 64
+ WHEN 1700 /*numeric*/ THEN
+ CASE WHEN atttypmod = -1
+ THEN null
+ ELSE ((atttypmod - 4) >> 16) & 65535
+ END
+ WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
+ WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
+ ELSE null
+ END AS numeric_precision,
+ CASE
+ WHEN atttypid IN (21, 23, 20) THEN 0
+ WHEN atttypid IN (1700) THEN
+ CASE
+ WHEN atttypmod = -1 THEN null
+ ELSE (atttypmod - 4) & 65535
+ END
+ ELSE null
+ END AS numeric_scale,
+ CAST(
information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
AS numeric
- ) AS size,
- a.attnum = any (ct.conkey) as is_pkey
+ ) AS size,
+ a.attnum = any (ct.conkey) as is_pkey
FROM
- pg_class c
- LEFT JOIN pg_attribute a ON a.attrelid = c.oid
- LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
- LEFT JOIN pg_type t ON a.atttypid = t.oid
- LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
- LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
+ pg_class c
+ LEFT JOIN pg_attribute a ON a.attrelid = c.oid
+ LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
+ LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
WHERE
- a.attnum > 0 and t.typname != ''
- and c.relname = {$tableName}
- and d.nspname = {$schemaName}
+ a.attnum > 0 and t.typname != ''
+ and c.relname = {$tableName}
+ and d.nspname = {$schemaName}
ORDER BY
- a.attnum;
+ a.attnum;
SQL;
- $columns = $this->db->createCommand($sql)->queryAll();
- if (empty($columns)) {
- return false;
- }
- foreach ($columns as $column) {
- $column = $this->loadColumnSchema($column);
- $table->columns[$column->name] = $column;
- if ($column->isPrimaryKey === true) {
- $table->primaryKey[] = $column->name;
- if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) {
- $table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue);
- }
- }
- }
- return true;
- }
-
- /**
- * Loads the column information into a [[ColumnSchema]] object.
- * @param array $info column information
- * @return ColumnSchema the column schema object
- */
- protected function loadColumnSchema($info)
- {
- $column = new ColumnSchema();
- $column->allowNull = $info['is_nullable'];
- $column->autoIncrement = $info['is_autoinc'];
- $column->comment = $info['column_comment'];
- $column->dbType = $info['data_type'];
- $column->defaultValue = $info['column_default'];
- $column->enumValues = explode(',', str_replace(["''"], ["'"], $info['enum_values']));
- $column->unsigned = false; // has no meaning in PG
- $column->isPrimaryKey = $info['is_pkey'];
- $column->name = $info['column_name'];
- $column->precision = $info['numeric_precision'];
- $column->scale = $info['numeric_scale'];
- $column->size = $info['size'];
-
- if (isset($this->typeMap[$column->dbType])) {
- $column->type = $this->typeMap[$column->dbType];
- } else {
- $column->type = self::TYPE_STRING;
- }
- $column->phpType = $this->getColumnPhpType($column);
- return $column;
- }
+ $columns = $this->db->createCommand($sql)->queryAll();
+ if (empty($columns)) {
+ return false;
+ }
+ foreach ($columns as $column) {
+ $column = $this->loadColumnSchema($column);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey === true) {
+ $table->primaryKey[] = $column->name;
+ if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) {
+ $table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema();
+ $column->allowNull = $info['is_nullable'];
+ $column->autoIncrement = $info['is_autoinc'];
+ $column->comment = $info['column_comment'];
+ $column->dbType = $info['data_type'];
+ $column->defaultValue = $info['column_default'];
+ $column->enumValues = explode(',', str_replace(["''"], ["'"], $info['enum_values']));
+ $column->unsigned = false; // has no meaning in PG
+ $column->isPrimaryKey = $info['is_pkey'];
+ $column->name = $info['column_name'];
+ $column->precision = $info['numeric_precision'];
+ $column->scale = $info['numeric_scale'];
+ $column->size = $info['size'];
+
+ if (isset($this->typeMap[$column->dbType])) {
+ $column->type = $this->typeMap[$column->dbType];
+ } else {
+ $column->type = self::TYPE_STRING;
+ }
+ $column->phpType = $this->getColumnPhpType($column);
+
+ return $column;
+ }
}
diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php
index bf9bece34b9..9a74dfad880 100644
--- a/framework/db/sqlite/QueryBuilder.php
+++ b/framework/db/sqlite/QueryBuilder.php
@@ -19,257 +19,258 @@
*/
class QueryBuilder extends \yii\db\QueryBuilder
{
- /**
- * @var array mapping from abstract column types (keys) to physical column types (values).
- */
- public $typeMap = [
- Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
- Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
- Schema::TYPE_STRING => 'varchar(255)',
- Schema::TYPE_TEXT => 'text',
- Schema::TYPE_SMALLINT => 'smallint',
- Schema::TYPE_INTEGER => 'integer',
- Schema::TYPE_BIGINT => 'bigint',
- Schema::TYPE_FLOAT => 'float',
- Schema::TYPE_DECIMAL => 'decimal(10,0)',
- Schema::TYPE_DATETIME => 'datetime',
- Schema::TYPE_TIMESTAMP => 'timestamp',
- Schema::TYPE_TIME => 'time',
- Schema::TYPE_DATE => 'date',
- Schema::TYPE_BINARY => 'blob',
- Schema::TYPE_BOOLEAN => 'boolean',
- Schema::TYPE_MONEY => 'decimal(19,4)',
- ];
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = [
+ Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
+ Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'integer',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'float',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'blob',
+ Schema::TYPE_BOOLEAN => 'boolean',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ ];
- /**
- * Generates a batch INSERT SQL statement.
- * For example,
- *
- * ~~~
- * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [
- * ['Tom', 30],
- * ['Jane', 20],
- * ['Linda', 25],
- * ])->execute();
- * ~~~
- *
- * Note that the values in each row must match the corresponding column names.
- *
- * @param string $table the table that new rows will be inserted into.
- * @param array $columns the column names
- * @param array $rows the rows to be batch inserted into the table
- * @return string the batch INSERT SQL statement
- */
- public function batchInsert($table, $columns, $rows)
- {
- if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
- $columnSchemas = $tableSchema->columns;
- } else {
- $columnSchemas = [];
- }
+ /**
+ * Generates a batch INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [
+ * ['Tom', 30],
+ * ['Jane', 20],
+ * ['Linda', 25],
+ * ])->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the table
+ * @return string the batch INSERT SQL statement
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = [];
+ }
- foreach ($columns as $i => $name) {
- $columns[$i] = $this->db->quoteColumnName($name);
- }
+ foreach ($columns as $i => $name) {
+ $columns[$i] = $this->db->quoteColumnName($name);
+ }
- $values = [];
- foreach ($rows as $row) {
- $vs = [];
- foreach ($row as $i => $value) {
- if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
- $value = $columnSchemas[$columns[$i]]->typecast($value);
- }
- $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value;
- }
- $values[] = implode(', ', $vs);
- }
+ $values = [];
+ foreach ($rows as $row) {
+ $vs = [];
+ foreach ($row as $i => $value) {
+ if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
+ $value = $columnSchemas[$columns[$i]]->typecast($value);
+ }
+ $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value;
+ }
+ $values[] = implode(', ', $vs);
+ }
- return 'INSERT INTO ' . $this->db->quoteTableName($table)
- . ' (' . implode(', ', $columns) . ') SELECT ' . implode(' UNION ALL ', $values);
- }
+ return 'INSERT INTO ' . $this->db->quoteTableName($table)
+ . ' (' . implode(', ', $columns) . ') SELECT ' . implode(' UNION ALL ', $values);
+ }
- /**
- * Creates a SQL statement for resetting the sequence value of a table's primary key.
- * The sequence will be reset such that the primary key of the next new row inserted
- * will have the specified value or 1.
- * @param string $tableName the name of the table whose primary key sequence will be reset
- * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
- * the next new row's primary key will have a value 1.
- * @return string the SQL statement for resetting sequence
- * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
- */
- public function resetSequence($tableName, $value = null)
- {
- $db = $this->db;
- $table = $db->getTableSchema($tableName);
- if ($table !== null && $table->sequenceName !== null) {
- if ($value === null) {
- $key = reset($table->primaryKey);
- $tableName = $db->quoteTableName($tableName);
- $value = $db->createCommand("SELECT MAX('$key') FROM $tableName")->queryScalar();
- } else {
- $value = (int)$value - 1;
- }
- try {
- $db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute();
- } catch (Exception $e) {
- // it's possible that sqlite_sequence does not exist
- }
- } elseif ($table === null) {
- throw new InvalidParamException("Table not found: $tableName");
- } else {
- throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
- }
- }
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $tableName the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function resetSequence($tableName, $value = null)
+ {
+ $db = $this->db;
+ $table = $db->getTableSchema($tableName);
+ if ($table !== null && $table->sequenceName !== null) {
+ if ($value === null) {
+ $key = reset($table->primaryKey);
+ $tableName = $db->quoteTableName($tableName);
+ $value = $db->createCommand("SELECT MAX('$key') FROM $tableName")->queryScalar();
+ } else {
+ $value = (int) $value - 1;
+ }
+ try {
+ $db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute();
+ } catch (Exception $e) {
+ // it's possible that sqlite_sequence does not exist
+ }
+ } elseif ($table === null) {
+ throw new InvalidParamException("Table not found: $tableName");
+ } else {
+ throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
+ }
+ }
- /**
- * Enables or disables integrity check.
- * @param boolean $check whether to turn on or off the integrity check.
- * @param string $schema the schema of the tables. Meaningless for SQLite.
- * @param string $table the table name. Meaningless for SQLite.
- * @return string the SQL statement for checking integrity
- * @throws NotSupportedException this is not supported by SQLite
- */
- public function checkIntegrity($check = true, $schema = '', $table = '')
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Enables or disables integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema of the tables. Meaningless for SQLite.
+ * @param string $table the table name. Meaningless for SQLite.
+ * @return string the SQL statement for checking integrity
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * Builds a SQL statement for truncating a DB table.
- * @param string $table the table to be truncated. The name will be properly quoted by the method.
- * @return string the SQL statement for truncating a DB table.
- */
- public function truncateTable($table)
- {
- return "DELETE FROM " . $this->db->quoteTableName($table);
- }
+ /**
+ * Builds a SQL statement for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ * @return string the SQL statement for truncating a DB table.
+ */
+ public function truncateTable($table)
+ {
+ return "DELETE FROM " . $this->db->quoteTableName($table);
+ }
- /**
- * Builds a SQL statement for dropping an index.
- * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping an index.
- */
- public function dropIndex($name, $table)
- {
- return 'DROP INDEX ' . $this->db->quoteTableName($name);
- }
+ /**
+ * Builds a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping an index.
+ */
+ public function dropIndex($name, $table)
+ {
+ return 'DROP INDEX ' . $this->db->quoteTableName($name);
+ }
- /**
- * Builds a SQL statement for dropping a DB column.
- * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
- * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping a DB column.
- * @throws NotSupportedException this is not supported by SQLite
- */
- public function dropColumn($table, $column)
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Builds a SQL statement for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a DB column.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function dropColumn($table, $column)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * Builds a SQL statement for renaming a column.
- * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
- * @param string $oldName the old name of the column. The name will be properly quoted by the method.
- * @param string $newName the new name of the column. The name will be properly quoted by the method.
- * @return string the SQL statement for renaming a DB column.
- * @throws NotSupportedException this is not supported by SQLite
- */
- public function renameColumn($table, $oldName, $newName)
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Builds a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB column.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * Builds a SQL statement for adding a foreign key constraint to an existing table.
- * The method will properly quote the table and column names.
- * @param string $name the name of the foreign key constraint.
- * @param string $table the table that the foreign key constraint will be added to.
- * @param string|array $columns the name of the column to that the constraint will be added on.
- * If there are multiple columns, separate them with commas or use an array to represent them.
- * @param string $refTable the table that the foreign key references to.
- * @param string|array $refColumns the name of the column that the foreign key references to.
- * If there are multiple columns, separate them with commas or use an array to represent them.
- * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
- * @return string the SQL statement for adding a foreign key constraint to an existing table.
- * @throws NotSupportedException this is not supported by SQLite
- */
- public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Builds a SQL statement for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string|array $columns the name of the column to that the constraint will be added on.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string|array $refColumns the name of the column that the foreign key references to.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @return string the SQL statement for adding a foreign key constraint to an existing table.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * Builds a SQL statement for dropping a foreign key constraint.
- * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
- * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
- * @return string the SQL statement for dropping a foreign key constraint.
- * @throws NotSupportedException this is not supported by SQLite
- */
- public function dropForeignKey($name, $table)
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a foreign key constraint.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function dropForeignKey($name, $table)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * Builds a SQL statement for changing the definition of a column.
- * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
- * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
- * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
- * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
- * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
- * will become 'varchar(255) not null'.
- * @return string the SQL statement for changing the definition of a column.
- * @throws NotSupportedException this is not supported by SQLite
- */
- public function alterColumn($table, $column, $type)
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
+ * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
+ * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
+ * will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * Builds a SQL statement for adding a primary key constraint to an existing table.
- * @param string $name the name of the primary key constraint.
- * @param string $table the table that the primary key constraint will be added to.
- * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
- * @return string the SQL statement for adding a primary key constraint to an existing table.
- * @throws NotSupportedException this is not supported by SQLite
- */
- public function addPrimaryKey($name, $table, $columns)
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Builds a SQL statement for adding a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * @return string the SQL statement for adding a primary key constraint to an existing table.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * Builds a SQL statement for removing a primary key constraint to an existing table.
- * @param string $name the name of the primary key constraint to be removed.
- * @param string $table the table that the primary key constraint will be removed from.
- * @return string the SQL statement for removing a primary key constraint from an existing table.
- * @throws NotSupportedException this is not supported by SQLite *
- */
- public function dropPrimaryKey($name, $table)
- {
- throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
- }
+ /**
+ * Builds a SQL statement for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return string the SQL statement for removing a primary key constraint from an existing table.
+ * @throws NotSupportedException this is not supported by SQLite *
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
- /**
- * @inheritdoc
- */
- public function buildLimit($limit, $offset)
- {
- $sql = '';
- if ($this->hasLimit($limit)) {
- $sql = 'LIMIT ' . $limit;
- if ($this->hasOffset($offset)) {
- $sql .= ' OFFSET ' . $offset;
- }
- } elseif ($this->hasOffset($offset)) {
- // limit is not optional in SQLite
- // http://www.sqlite.org/syntaxdiagrams.html#select-stmt
- $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
- }
- return $sql;
- }
+ /**
+ * @inheritdoc
+ */
+ public function buildLimit($limit, $offset)
+ {
+ $sql = '';
+ if ($this->hasLimit($limit)) {
+ $sql = 'LIMIT ' . $limit;
+ if ($this->hasOffset($offset)) {
+ $sql .= ' OFFSET ' . $offset;
+ }
+ } elseif ($this->hasOffset($offset)) {
+ // limit is not optional in SQLite
+ // http://www.sqlite.org/syntaxdiagrams.html#select-stmt
+ $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
+ }
+
+ return $sql;
+ }
}
diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php
index bf1eb4c0875..67e3d541892 100644
--- a/framework/db/sqlite/Schema.php
+++ b/framework/db/sqlite/Schema.php
@@ -18,230 +18,233 @@
*/
class Schema extends \yii\db\Schema
{
- /**
- * @var array mapping from physical column types (keys) to abstract column types (values)
- */
- public $typeMap = [
- 'tinyint' => self::TYPE_SMALLINT,
- 'bit' => self::TYPE_SMALLINT,
- 'boolean' => self::TYPE_BOOLEAN,
- 'bool' => self::TYPE_BOOLEAN,
- 'smallint' => self::TYPE_SMALLINT,
- 'mediumint' => self::TYPE_INTEGER,
- 'int' => self::TYPE_INTEGER,
- 'integer' => self::TYPE_INTEGER,
- 'bigint' => self::TYPE_BIGINT,
- 'float' => self::TYPE_FLOAT,
- 'double' => self::TYPE_FLOAT,
- 'real' => self::TYPE_FLOAT,
- 'decimal' => self::TYPE_DECIMAL,
- 'numeric' => self::TYPE_DECIMAL,
- 'tinytext' => self::TYPE_TEXT,
- 'mediumtext' => self::TYPE_TEXT,
- 'longtext' => self::TYPE_TEXT,
- 'text' => self::TYPE_TEXT,
- 'varchar' => self::TYPE_STRING,
- 'string' => self::TYPE_STRING,
- 'char' => self::TYPE_STRING,
- 'datetime' => self::TYPE_DATETIME,
- 'year' => self::TYPE_DATE,
- 'date' => self::TYPE_DATE,
- 'time' => self::TYPE_TIME,
- 'timestamp' => self::TYPE_TIMESTAMP,
- 'enum' => self::TYPE_STRING,
- ];
-
- /**
- * Quotes a table name for use in a query.
- * A simple table name has no schema prefix.
- * @param string $name table name
- * @return string the properly quoted table name
- */
- public function quoteSimpleTableName($name)
- {
- return strpos($name, "`") !== false ? $name : "`" . $name . "`";
- }
-
- /**
- * Quotes a column name for use in a query.
- * A simple column name has no prefix.
- * @param string $name column name
- * @return string the properly quoted column name
- */
- public function quoteSimpleColumnName($name)
- {
- return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
- }
-
- /**
- * Creates a query builder for the MySQL database.
- * This method may be overridden by child classes to create a DBMS-specific query builder.
- * @return QueryBuilder query builder instance
- */
- public function createQueryBuilder()
- {
- return new QueryBuilder($this->db);
- }
-
- /**
- * Returns all table names in the database.
- * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
- * @return array all table names in the database. The names have NO schema name prefix.
- */
- protected function findTableNames($schema = '')
- {
- $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'";
- return $this->db->createCommand($sql)->queryColumn();
- }
-
- /**
- * Loads the metadata for the specified table.
- * @param string $name table name
- * @return TableSchema driver dependent table metadata. Null if the table does not exist.
- */
- protected function loadTableSchema($name)
- {
- $table = new TableSchema;
- $table->name = $name;
- $table->fullName = $name;
-
- if ($this->findColumns($table)) {
- $this->findConstraints($table);
- return $table;
- } else {
- return null;
- }
- }
-
- /**
- * Collects the table column metadata.
- * @param TableSchema $table the table metadata
- * @return boolean whether the table exists in the database
- */
- protected function findColumns($table)
- {
- $sql = "PRAGMA table_info(" . $this->quoteSimpleTableName($table->name) . ')';
- $columns = $this->db->createCommand($sql)->queryAll();
- if (empty($columns)) {
- return false;
- }
-
- foreach ($columns as $info) {
- $column = $this->loadColumnSchema($info);
- $table->columns[$column->name] = $column;
- if ($column->isPrimaryKey) {
- $table->primaryKey[] = $column->name;
- }
- }
- if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) {
- $table->sequenceName = '';
- $table->columns[$table->primaryKey[0]]->autoIncrement = true;
- }
-
- return true;
- }
-
- /**
- * Collects the foreign key column details for the given table.
- * @param TableSchema $table the table metadata
- */
- protected function findConstraints($table)
- {
- $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')';
- $keys = $this->db->createCommand($sql)->queryAll();
- foreach ($keys as $key) {
- $id = (int)$key['id'];
- if (!isset($table->foreignKeys[$id])) {
- $table->foreignKeys[$id] = [$key['table'], $key['from'] => $key['to']];
- } else {
- // composite FK
- $table->foreignKeys[$id][$key['from']] = $key['to'];
- }
- }
- }
-
- /**
- * Returns all unique indexes for the given table.
- * Each array element is of the following structure:
- *
- * ~~~
- * [
- * 'IndexName1' => ['col1' [, ...]],
- * 'IndexName2' => ['col2' [, ...]],
- * ]
- * ~~~
- *
- * @param TableSchema $table the table metadata
- * @return array all unique indexes for the given table.
- */
- public function findUniqueIndexes($table)
- {
- $sql = "PRAGMA index_list(" . $this->quoteSimpleTableName($table->name) . ')';
- $indexes = $this->db->createCommand($sql)->queryAll();
- $uniqueIndexes = [];
-
- foreach ($indexes as $index) {
- $indexName = $index['name'];
- $indexInfo = $this->db->createCommand("PRAGMA index_info(" . $this->quoteValue($index['name']) . ")")->queryAll();
-
- if ($index['unique']) {
- $uniqueIndexes[$indexName] = [];
- foreach ($indexInfo as $row) {
- $uniqueIndexes[$indexName][] = $row['name'];
- }
- }
- }
- return $uniqueIndexes;
- }
-
- /**
- * Loads the column information into a [[ColumnSchema]] object.
- * @param array $info column information
- * @return ColumnSchema the column schema object
- */
- protected function loadColumnSchema($info)
- {
- $column = new ColumnSchema;
- $column->name = $info['name'];
- $column->allowNull = !$info['notnull'];
- $column->isPrimaryKey = $info['pk'] != 0;
-
- $column->dbType = $info['type'];
- $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
-
- $column->type = self::TYPE_STRING;
- if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
- $type = strtolower($matches[1]);
- if (isset($this->typeMap[$type])) {
- $column->type = $this->typeMap[$type];
- }
-
- if (!empty($matches[2])) {
- $values = explode(',', $matches[2]);
- $column->size = $column->precision = (int)$values[0];
- if (isset($values[1])) {
- $column->scale = (int)$values[1];
- }
- if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
- $column->type = 'boolean';
- } elseif ($type === 'bit') {
- if ($column->size > 32) {
- $column->type = 'bigint';
- } elseif ($column->size === 32) {
- $column->type = 'integer';
- }
- }
- }
- }
- $column->phpType = $this->getColumnPhpType($column);
-
- $value = $info['dflt_value'];
- if ($column->type === 'string') {
- $column->defaultValue = trim($value, "'\"");
- } else {
- $column->defaultValue = $column->typecast(strcasecmp($value, 'null') ? $value : null);
- }
-
- return $column;
- }
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ */
+ public $typeMap = [
+ 'tinyint' => self::TYPE_SMALLINT,
+ 'bit' => self::TYPE_SMALLINT,
+ 'boolean' => self::TYPE_BOOLEAN,
+ 'bool' => self::TYPE_BOOLEAN,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'mediumint' => self::TYPE_INTEGER,
+ 'int' => self::TYPE_INTEGER,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'float' => self::TYPE_FLOAT,
+ 'double' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'tinytext' => self::TYPE_TEXT,
+ 'mediumtext' => self::TYPE_TEXT,
+ 'longtext' => self::TYPE_TEXT,
+ 'text' => self::TYPE_TEXT,
+ 'varchar' => self::TYPE_STRING,
+ 'string' => self::TYPE_STRING,
+ 'char' => self::TYPE_STRING,
+ 'datetime' => self::TYPE_DATETIME,
+ 'year' => self::TYPE_DATE,
+ 'date' => self::TYPE_DATE,
+ 'time' => self::TYPE_TIME,
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'enum' => self::TYPE_STRING,
+ ];
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, "`") !== false ? $name : "`" . $name . "`";
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
+ }
+
+ /**
+ * Creates a query builder for the MySQL database.
+ * This method may be overridden by child classes to create a DBMS-specific query builder.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'";
+
+ return $this->db->createCommand($sql)->queryColumn();
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema driver dependent table metadata. Null if the table does not exist.
+ */
+ protected function loadTableSchema($name)
+ {
+ $table = new TableSchema;
+ $table->name = $name;
+ $table->fullName = $name;
+
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
+
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Collects the table column metadata.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ $sql = "PRAGMA table_info(" . $this->quoteSimpleTableName($table->name) . ')';
+ $columns = $this->db->createCommand($sql)->queryAll();
+ if (empty($columns)) {
+ return false;
+ }
+
+ foreach ($columns as $info) {
+ $column = $this->loadColumnSchema($info);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey) {
+ $table->primaryKey[] = $column->name;
+ }
+ }
+ if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) {
+ $table->sequenceName = '';
+ $table->columns[$table->primaryKey[0]]->autoIncrement = true;
+ }
+
+ return true;
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+ $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')';
+ $keys = $this->db->createCommand($sql)->queryAll();
+ foreach ($keys as $key) {
+ $id = (int) $key['id'];
+ if (!isset($table->foreignKeys[$id])) {
+ $table->foreignKeys[$id] = [$key['table'], $key['from'] => $key['to']];
+ } else {
+ // composite FK
+ $table->foreignKeys[$id][$key['from']] = $key['to'];
+ }
+ }
+ }
+
+ /**
+ * Returns all unique indexes for the given table.
+ * Each array element is of the following structure:
+ *
+ * ~~~
+ * [
+ * 'IndexName1' => ['col1' [, ...]],
+ * 'IndexName2' => ['col2' [, ...]],
+ * ]
+ * ~~~
+ *
+ * @param TableSchema $table the table metadata
+ * @return array all unique indexes for the given table.
+ */
+ public function findUniqueIndexes($table)
+ {
+ $sql = "PRAGMA index_list(" . $this->quoteSimpleTableName($table->name) . ')';
+ $indexes = $this->db->createCommand($sql)->queryAll();
+ $uniqueIndexes = [];
+
+ foreach ($indexes as $index) {
+ $indexName = $index['name'];
+ $indexInfo = $this->db->createCommand("PRAGMA index_info(" . $this->quoteValue($index['name']) . ")")->queryAll();
+
+ if ($index['unique']) {
+ $uniqueIndexes[$indexName] = [];
+ foreach ($indexInfo as $row) {
+ $uniqueIndexes[$indexName][] = $row['name'];
+ }
+ }
+ }
+
+ return $uniqueIndexes;
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema;
+ $column->name = $info['name'];
+ $column->allowNull = !$info['notnull'];
+ $column->isPrimaryKey = $info['pk'] != 0;
+
+ $column->dbType = $info['type'];
+ $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
+
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = strtolower($matches[1]);
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+
+ if (!empty($matches[2])) {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int) $values[0];
+ if (isset($values[1])) {
+ $column->scale = (int) $values[1];
+ }
+ if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
+ $column->type = 'boolean';
+ } elseif ($type === 'bit') {
+ if ($column->size > 32) {
+ $column->type = 'bigint';
+ } elseif ($column->size === 32) {
+ $column->type = 'integer';
+ }
+ }
+ }
+ }
+ $column->phpType = $this->getColumnPhpType($column);
+
+ $value = $info['dflt_value'];
+ if ($column->type === 'string') {
+ $column->defaultValue = trim($value, "'\"");
+ } else {
+ $column->defaultValue = $column->typecast(strcasecmp($value, 'null') ? $value : null);
+ }
+
+ return $column;
+ }
}
diff --git a/framework/grid/ActionColumn.php b/framework/grid/ActionColumn.php
index dbabc1c7982..af8f25c172f 100644
--- a/framework/grid/ActionColumn.php
+++ b/framework/grid/ActionColumn.php
@@ -32,120 +32,121 @@
*/
class ActionColumn extends Column
{
- /**
- * @var string the ID of the controller that should handle the actions specified here.
- * If not set, it will use the currently active controller. This property is mainly used by
- * [[urlCreator]] to create URLs for different actions. The value of this property will be prefixed
- * to each action name to form the route of the action.
- */
- public $controller;
- /**
- * @var string the template used for composing each cell in the action column.
- * Tokens enclosed within curly brackets are treated as controller action IDs (also called *button names*
- * in the context of action column). They will be replaced by the corresponding button rendering callbacks
- * specified in [[buttons]]. For example, the token `{view}` will be replaced by the result of
- * the callback `buttons['view']`. If a callback cannot be found, the token will be replaced with an empty string.
- * @see buttons
- */
- public $template = '{view} {update} {delete}';
- /**
- * @var array button rendering callbacks. The array keys are the button names (without curly brackets),
- * and the values are the corresponding button rendering callbacks. The callbacks should use the following
- * signature:
- *
- * ```php
- * function ($url, $model) {
- * // return the button HTML code
- * }
- * ```
- *
- * where `$url` is the URL that the column creates for the button, and `$model` is the model object
- * being rendered for the current row.
- */
- public $buttons = [];
- /**
- * @var callable a callback that creates a button URL using the specified model information.
- * The signature of the callback should be the same as that of [[createUrl()]].
- * If this property is not set, button URLs will be created using [[createUrl()]].
- */
- public $urlCreator;
+ /**
+ * @var string the ID of the controller that should handle the actions specified here.
+ * If not set, it will use the currently active controller. This property is mainly used by
+ * [[urlCreator]] to create URLs for different actions. The value of this property will be prefixed
+ * to each action name to form the route of the action.
+ */
+ public $controller;
+ /**
+ * @var string the template used for composing each cell in the action column.
+ * Tokens enclosed within curly brackets are treated as controller action IDs (also called *button names*
+ * in the context of action column). They will be replaced by the corresponding button rendering callbacks
+ * specified in [[buttons]]. For example, the token `{view}` will be replaced by the result of
+ * the callback `buttons['view']`. If a callback cannot be found, the token will be replaced with an empty string.
+ * @see buttons
+ */
+ public $template = '{view} {update} {delete}';
+ /**
+ * @var array button rendering callbacks. The array keys are the button names (without curly brackets),
+ * and the values are the corresponding button rendering callbacks. The callbacks should use the following
+ * signature:
+ *
+ * ```php
+ * function ($url, $model) {
+ * // return the button HTML code
+ * }
+ * ```
+ *
+ * where `$url` is the URL that the column creates for the button, and `$model` is the model object
+ * being rendered for the current row.
+ */
+ public $buttons = [];
+ /**
+ * @var callable a callback that creates a button URL using the specified model information.
+ * The signature of the callback should be the same as that of [[createUrl()]].
+ * If this property is not set, button URLs will be created using [[createUrl()]].
+ */
+ public $urlCreator;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ $this->initDefaultButtons();
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- $this->initDefaultButtons();
- }
+ /**
+ * Initializes the default button rendering callbacks
+ */
+ protected function initDefaultButtons()
+ {
+ if (!isset($this->buttons['view'])) {
+ $this->buttons['view'] = function ($url, $model) {
+ return Html::a('', $url, [
+ 'title' => Yii::t('yii', 'View'),
+ 'data-pjax' => '0',
+ ]);
+ };
+ }
+ if (!isset($this->buttons['update'])) {
+ $this->buttons['update'] = function ($url, $model) {
+ return Html::a('', $url, [
+ 'title' => Yii::t('yii', 'Update'),
+ 'data-pjax' => '0',
+ ]);
+ };
+ }
+ if (!isset($this->buttons['delete'])) {
+ $this->buttons['delete'] = function ($url, $model) {
+ return Html::a('', $url, [
+ 'title' => Yii::t('yii', 'Delete'),
+ 'data-confirm' => Yii::t('yii', 'Are you sure you want to delete this item?'),
+ 'data-method' => 'post',
+ 'data-pjax' => '0',
+ ]);
+ };
+ }
+ }
- /**
- * Initializes the default button rendering callbacks
- */
- protected function initDefaultButtons()
- {
- if (!isset($this->buttons['view'])) {
- $this->buttons['view'] = function ($url, $model) {
- return Html::a('', $url, [
- 'title' => Yii::t('yii', 'View'),
- 'data-pjax' => '0',
- ]);
- };
- }
- if (!isset($this->buttons['update'])) {
- $this->buttons['update'] = function ($url, $model) {
- return Html::a('', $url, [
- 'title' => Yii::t('yii', 'Update'),
- 'data-pjax' => '0',
- ]);
- };
- }
- if (!isset($this->buttons['delete'])) {
- $this->buttons['delete'] = function ($url, $model) {
- return Html::a('', $url, [
- 'title' => Yii::t('yii', 'Delete'),
- 'data-confirm' => Yii::t('yii', 'Are you sure you want to delete this item?'),
- 'data-method' => 'post',
- 'data-pjax' => '0',
- ]);
- };
- }
- }
+ /**
+ * Creates a URL for the given action and model.
+ * This method is called for each button and each row.
+ * @param string $action the button name (or action ID)
+ * @param \yii\db\ActiveRecord $model the data model
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the current row index
+ * @return string the created URL
+ */
+ public function createUrl($action, $model, $key, $index)
+ {
+ if ($this->urlCreator instanceof Closure) {
+ return call_user_func($this->urlCreator, $action, $model, $key, $index);
+ } else {
+ $params = is_array($key) ? $key : ['id' => (string) $key];
+ $params[0] = $this->controller ? $this->controller . '/' . $action : $action;
- /**
- * Creates a URL for the given action and model.
- * This method is called for each button and each row.
- * @param string $action the button name (or action ID)
- * @param \yii\db\ActiveRecord $model the data model
- * @param mixed $key the key associated with the data model
- * @param integer $index the current row index
- * @return string the created URL
- */
- public function createUrl($action, $model, $key, $index)
- {
- if ($this->urlCreator instanceof Closure) {
- return call_user_func($this->urlCreator, $action, $model, $key, $index);
- } else {
- $params = is_array($key) ? $key : ['id' => (string)$key];
- $params[0] = $this->controller ? $this->controller . '/' . $action : $action;
- return Url::toRoute($params);
- }
- }
+ return Url::toRoute($params);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function renderDataCellContent($model, $key, $index)
- {
- return preg_replace_callback('/\\{([\w\-\/]+)\\}/', function ($matches) use ($model, $key, $index) {
- $name = $matches[1];
- if (isset($this->buttons[$name])) {
- $url = $this->createUrl($name, $model, $key, $index);
- return call_user_func($this->buttons[$name], $url, $model);
- } else {
- return '';
- }
- }, $this->template);
- }
+ /**
+ * @inheritdoc
+ */
+ protected function renderDataCellContent($model, $key, $index)
+ {
+ return preg_replace_callback('/\\{([\w\-\/]+)\\}/', function ($matches) use ($model, $key, $index) {
+ $name = $matches[1];
+ if (isset($this->buttons[$name])) {
+ $url = $this->createUrl($name, $model, $key, $index);
+
+ return call_user_func($this->buttons[$name], $url, $model);
+ } else {
+ return '';
+ }
+ }, $this->template);
+ }
}
diff --git a/framework/grid/CheckboxColumn.php b/framework/grid/CheckboxColumn.php
index 152242544a7..ddb30dbf1c7 100644
--- a/framework/grid/CheckboxColumn.php
+++ b/framework/grid/CheckboxColumn.php
@@ -39,73 +39,73 @@
*/
class CheckboxColumn extends Column
{
- /**
- * @var string the name of the input checkbox input fields. This will be appended with `[]` to ensure it is an array.
- */
- public $name = 'selection';
- /**
- * @var array HTML attributes for the checkboxes.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $checkboxOptions = [];
- /**
- * @var bool whether it is possible to select multiple rows. Defaults to `true`.
- */
- public $multiple = true;
+ /**
+ * @var string the name of the input checkbox input fields. This will be appended with `[]` to ensure it is an array.
+ */
+ public $name = 'selection';
+ /**
+ * @var array HTML attributes for the checkboxes.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $checkboxOptions = [];
+ /**
+ * @var bool whether it is possible to select multiple rows. Defaults to `true`.
+ */
+ public $multiple = true;
+ /**
+ * @inheritdoc
+ * @throws \yii\base\InvalidConfigException if [[name]] is not set.
+ */
+ public function init()
+ {
+ parent::init();
+ if (empty($this->name)) {
+ throw new InvalidConfigException('The "name" property must be set.');
+ }
+ if (substr($this->name, -2) !== '[]') {
+ $this->name .= '[]';
+ }
+ }
- /**
- * @inheritdoc
- * @throws \yii\base\InvalidConfigException if [[name]] is not set.
- */
- public function init()
- {
- parent::init();
- if (empty($this->name)) {
- throw new InvalidConfigException('The "name" property must be set.');
- }
- if (substr($this->name, -2) !== '[]') {
- $this->name .= '[]';
- }
- }
+ /**
+ * Renders the header cell content.
+ * The default implementation simply renders [[header]].
+ * This method may be overridden to customize the rendering of the header cell.
+ * @return string the rendering result
+ */
+ protected function renderHeaderCellContent()
+ {
+ $name = rtrim($this->name, '[]') . '_all';
+ $id = $this->grid->options['id'];
+ $options = json_encode([
+ 'name' => $this->name,
+ 'multiple' => $this->multiple,
+ 'checkAll' => $name,
+ ]);
+ $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);");
- /**
- * Renders the header cell content.
- * The default implementation simply renders [[header]].
- * This method may be overridden to customize the rendering of the header cell.
- * @return string the rendering result
- */
- protected function renderHeaderCellContent()
- {
- $name = rtrim($this->name, '[]') . '_all';
- $id = $this->grid->options['id'];
- $options = json_encode([
- 'name' => $this->name,
- 'multiple' => $this->multiple,
- 'checkAll' => $name,
- ]);
- $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);");
+ if ($this->header !== null || !$this->multiple) {
+ return parent::renderHeaderCellContent();
+ } else {
+ return Html::checkBox($name, false, ['class' => 'select-on-check-all']);
+ }
+ }
- if ($this->header !== null || !$this->multiple) {
- return parent::renderHeaderCellContent();
- } else {
- return Html::checkBox($name, false, ['class' => 'select-on-check-all']);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function renderDataCellContent($model, $key, $index)
+ {
+ if ($this->checkboxOptions instanceof Closure) {
+ $options = call_user_func($this->checkboxOptions, $model, $key, $index, $this);
+ } else {
+ $options = $this->checkboxOptions;
+ if (!isset($options['value'])) {
+ $options['value'] = is_array($key) ? json_encode($key) : $key;
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function renderDataCellContent($model, $key, $index)
- {
- if ($this->checkboxOptions instanceof Closure) {
- $options = call_user_func($this->checkboxOptions, $model, $key, $index, $this);
- } else {
- $options = $this->checkboxOptions;
- if (!isset($options['value'])) {
- $options['value'] = is_array($key) ? json_encode($key) : $key;
- }
- }
- return Html::checkbox($this->name, !empty($options['checked']), $options);
- }
+ return Html::checkbox($this->name, !empty($options['checked']), $options);
+ }
}
diff --git a/framework/grid/Column.php b/framework/grid/Column.php
index 23eaf513980..4da20f42670 100644
--- a/framework/grid/Column.php
+++ b/framework/grid/Column.php
@@ -19,157 +19,157 @@
*/
class Column extends Object
{
- /**
- * @var GridView the grid view object that owns this column.
- */
- public $grid;
- /**
- * @var string the header cell content. Note that it will not be HTML-encoded.
- */
- public $header;
- /**
- * @var string the footer cell content. Note that it will not be HTML-encoded.
- */
- public $footer;
- /**
- * @var callable
- */
- public $content;
- /**
- * @var boolean whether this column is visible. Defaults to true.
- */
- public $visible = true;
- /**
- * @var array the HTML attributes for the column group tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array the HTML attributes for the header cell tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $headerOptions = [];
- /**
- * @var array|\Closure the HTML attributes for the data cell tag. This can either be an array of
- * attributes or an anonymous function that ([[Closure]]) that returns such an array.
- * The signature of the function should be the following: `function ($model, $key, $index, $gridView)`.
- * A function may be used to assign different attributes to different rows based on the data in that row.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $contentOptions = [];
- /**
- * @var array the HTML attributes for the footer cell tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $footerOptions = [];
- /**
- * @var array the HTML attributes for the filter cell tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $filterOptions = [];
+ /**
+ * @var GridView the grid view object that owns this column.
+ */
+ public $grid;
+ /**
+ * @var string the header cell content. Note that it will not be HTML-encoded.
+ */
+ public $header;
+ /**
+ * @var string the footer cell content. Note that it will not be HTML-encoded.
+ */
+ public $footer;
+ /**
+ * @var callable
+ */
+ public $content;
+ /**
+ * @var boolean whether this column is visible. Defaults to true.
+ */
+ public $visible = true;
+ /**
+ * @var array the HTML attributes for the column group tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array the HTML attributes for the header cell tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $headerOptions = [];
+ /**
+ * @var array|\Closure the HTML attributes for the data cell tag. This can either be an array of
+ * attributes or an anonymous function that ([[Closure]]) that returns such an array.
+ * The signature of the function should be the following: `function ($model, $key, $index, $gridView)`.
+ * A function may be used to assign different attributes to different rows based on the data in that row.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $contentOptions = [];
+ /**
+ * @var array the HTML attributes for the footer cell tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $footerOptions = [];
+ /**
+ * @var array the HTML attributes for the filter cell tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $filterOptions = [];
+ /**
+ * Renders the header cell.
+ */
+ public function renderHeaderCell()
+ {
+ return Html::tag('th', $this->renderHeaderCellContent(), $this->headerOptions);
+ }
- /**
- * Renders the header cell.
- */
- public function renderHeaderCell()
- {
- return Html::tag('th', $this->renderHeaderCellContent(), $this->headerOptions);
- }
+ /**
+ * Renders the footer cell.
+ */
+ public function renderFooterCell()
+ {
+ return Html::tag('td', $this->renderFooterCellContent(), $this->footerOptions);
+ }
- /**
- * Renders the footer cell.
- */
- public function renderFooterCell()
- {
- return Html::tag('td', $this->renderFooterCellContent(), $this->footerOptions);
- }
+ /**
+ * Renders a data cell.
+ * @param mixed $model the data model being rendered
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data item among the item array returned by [[GridView::dataProvider]].
+ * @return string the rendering result
+ */
+ public function renderDataCell($model, $key, $index)
+ {
+ if ($this->contentOptions instanceof Closure) {
+ $options = call_user_func($this->contentOptions, $model, $key, $index, $this);
+ } else {
+ $options = $this->contentOptions;
+ }
- /**
- * Renders a data cell.
- * @param mixed $model the data model being rendered
- * @param mixed $key the key associated with the data model
- * @param integer $index the zero-based index of the data item among the item array returned by [[GridView::dataProvider]].
- * @return string the rendering result
- */
- public function renderDataCell($model, $key, $index)
- {
- if ($this->contentOptions instanceof Closure) {
- $options = call_user_func($this->contentOptions, $model, $key, $index, $this);
- } else {
- $options = $this->contentOptions;
- }
- return Html::tag('td', $this->renderDataCellContent($model, $key, $index), $options);
- }
+ return Html::tag('td', $this->renderDataCellContent($model, $key, $index), $options);
+ }
- /**
- * Renders the filter cell.
- */
- public function renderFilterCell()
- {
- return Html::tag('td', $this->renderFilterCellContent(), $this->filterOptions);
- }
+ /**
+ * Renders the filter cell.
+ */
+ public function renderFilterCell()
+ {
+ return Html::tag('td', $this->renderFilterCellContent(), $this->filterOptions);
+ }
- /**
- * Renders the header cell content.
- * The default implementation simply renders [[header]].
- * This method may be overridden to customize the rendering of the header cell.
- * @return string the rendering result
- */
- protected function renderHeaderCellContent()
- {
- return trim($this->header) !== '' ? $this->header : $this->grid->emptyCell;
- }
+ /**
+ * Renders the header cell content.
+ * The default implementation simply renders [[header]].
+ * This method may be overridden to customize the rendering of the header cell.
+ * @return string the rendering result
+ */
+ protected function renderHeaderCellContent()
+ {
+ return trim($this->header) !== '' ? $this->header : $this->grid->emptyCell;
+ }
- /**
- * Renders the footer cell content.
- * The default implementation simply renders [[footer]].
- * This method may be overridden to customize the rendering of the footer cell.
- * @return string the rendering result
- */
- protected function renderFooterCellContent()
- {
- return trim($this->footer) !== '' ? $this->footer : $this->grid->emptyCell;
- }
+ /**
+ * Renders the footer cell content.
+ * The default implementation simply renders [[footer]].
+ * This method may be overridden to customize the rendering of the footer cell.
+ * @return string the rendering result
+ */
+ protected function renderFooterCellContent()
+ {
+ return trim($this->footer) !== '' ? $this->footer : $this->grid->emptyCell;
+ }
- /**
- * Returns the raw data cell content.
- * This method is called by [[renderDataCellContent()]] when rendering the content of a data cell.
- * @param mixed $model the data model
- * @param mixed $key the key associated with the data model
- * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]].
- * @return string the rendering result
- */
- protected function getDataCellContent($model, $key, $index)
- {
- if ($this->content !== null) {
- return call_user_func($this->content, $model, $key, $index, $this);
- } else {
- return null;
- }
- }
+ /**
+ * Returns the raw data cell content.
+ * This method is called by [[renderDataCellContent()]] when rendering the content of a data cell.
+ * @param mixed $model the data model
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]].
+ * @return string the rendering result
+ */
+ protected function getDataCellContent($model, $key, $index)
+ {
+ if ($this->content !== null) {
+ return call_user_func($this->content, $model, $key, $index, $this);
+ } else {
+ return null;
+ }
+ }
- /**
- * Renders the data cell content.
- * @param mixed $model the data model
- * @param mixed $key the key associated with the data model
- * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]].
- * @return string the rendering result
- */
- protected function renderDataCellContent($model, $key, $index)
- {
- return $this->content !== null ? $this->getDataCellContent($model, $key, $index) : $this->grid->emptyCell;
- }
+ /**
+ * Renders the data cell content.
+ * @param mixed $model the data model
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]].
+ * @return string the rendering result
+ */
+ protected function renderDataCellContent($model, $key, $index)
+ {
+ return $this->content !== null ? $this->getDataCellContent($model, $key, $index) : $this->grid->emptyCell;
+ }
- /**
- * Renders the filter cell content.
- * The default implementation simply renders a space.
- * This method may be overridden to customize the rendering of the filter cell (if any).
- * @return string the rendering result
- */
- protected function renderFilterCellContent()
- {
- return $this->grid->emptyCell;
- }
+ /**
+ * Renders the filter cell content.
+ * The default implementation simply renders a space.
+ * This method may be overridden to customize the rendering of the filter cell (if any).
+ * @return string the rendering result
+ */
+ protected function renderFilterCellContent()
+ {
+ return $this->grid->emptyCell;
+ }
}
diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php
index 44cf2cb6d2c..2facd2a4686 100644
--- a/framework/grid/DataColumn.php
+++ b/framework/grid/DataColumn.php
@@ -24,149 +24,150 @@
*/
class DataColumn extends Column
{
- /**
- * @var string the attribute name associated with this column. When neither [[content]] nor [[value]]
- * is specified, the value of the specified attribute will be retrieved from each data model and displayed.
- *
- * Also, if [[label]] is not specified, the label associated with the attribute will be displayed.
- */
- public $attribute;
- /**
- * @var string label to be displayed in the [[header|header cell]] and also to be used as the sorting
- * link label when sorting is enabled for this column.
- * If it is not set and the models provided by the GridViews data provider are instances
- * of [[\yii\db\ActiveRecord]], the label will be determined using [[\yii\db\ActiveRecord::getAttributeLabel()]].
- * Otherwise [[\yii\helpers\Inflector::camel2words()]] will be used to get a label.
- */
- public $label;
- /**
- * @var string|\Closure the attribute name to be displayed in this column or an anonymous function that returns
- * the value to be displayed for every data model.
- * The signature of this function is `function ($model, $index, $widget)`.
- * If this is not set, `$model[$attribute]` will be used to obtain the value.
- */
- public $value;
- /**
- * @var string|array in which format should the value of each data model be displayed as (e.g. "raw", "text", "html",
- * ['date', 'Y-m-d']). Supported formats are determined by the [[GridView::formatter|formatter]] used by
- * the [[GridView]]. Default format is "text" which will format the value as an HTML-encoded plain text when
- * [[\yii\base\Formatter::format()]] or [[\yii\i18n\Formatter::format()]] is used.
- */
- public $format = 'text';
- /**
- * @var boolean whether to allow sorting by this column. If true and [[attribute]] is found in
- * the sort definition of [[GridView::dataProvider]], then the header cell of this column
- * will contain a link that may trigger the sorting when being clicked.
- */
- public $enableSorting = true;
- /**
- * @var array the HTML attributes for the link tag in the header cell
- * generated by [[\yii\data\Sort::link]] when sorting is enabled for this column.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $sortLinkOptions = [];
- /**
- * @var string|array|boolean the HTML code representing a filter input (e.g. a text field, a dropdown list)
- * that is used for this data column. This property is effective only when [[GridView::filterModel]] is set.
- *
- * - If this property is not set, a text field will be generated as the filter input;
- * - If this property is an array, a dropdown list will be generated that uses this property value as
- * the list options.
- * - If you don't want a filter for this data column, set this value to be false.
- */
- public $filter;
- /**
- * @var array the HTML attributes for the filter input fields. This property is used in combination with
- * the [[filter]] property. When [[filter]] is not set or is an array, this property will be used to
- * render the HTML attributes for the generated filter input fields.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $filterInputOptions = ['class' => 'form-control', 'id' => null];
+ /**
+ * @var string the attribute name associated with this column. When neither [[content]] nor [[value]]
+ * is specified, the value of the specified attribute will be retrieved from each data model and displayed.
+ *
+ * Also, if [[label]] is not specified, the label associated with the attribute will be displayed.
+ */
+ public $attribute;
+ /**
+ * @var string label to be displayed in the [[header|header cell]] and also to be used as the sorting
+ * link label when sorting is enabled for this column.
+ * If it is not set and the models provided by the GridViews data provider are instances
+ * of [[\yii\db\ActiveRecord]], the label will be determined using [[\yii\db\ActiveRecord::getAttributeLabel()]].
+ * Otherwise [[\yii\helpers\Inflector::camel2words()]] will be used to get a label.
+ */
+ public $label;
+ /**
+ * @var string|\Closure the attribute name to be displayed in this column or an anonymous function that returns
+ * the value to be displayed for every data model.
+ * The signature of this function is `function ($model, $index, $widget)`.
+ * If this is not set, `$model[$attribute]` will be used to obtain the value.
+ */
+ public $value;
+ /**
+ * @var string|array in which format should the value of each data model be displayed as (e.g. "raw", "text", "html",
+ * ['date', 'Y-m-d']). Supported formats are determined by the [[GridView::formatter|formatter]] used by
+ * the [[GridView]]. Default format is "text" which will format the value as an HTML-encoded plain text when
+ * [[\yii\base\Formatter::format()]] or [[\yii\i18n\Formatter::format()]] is used.
+ */
+ public $format = 'text';
+ /**
+ * @var boolean whether to allow sorting by this column. If true and [[attribute]] is found in
+ * the sort definition of [[GridView::dataProvider]], then the header cell of this column
+ * will contain a link that may trigger the sorting when being clicked.
+ */
+ public $enableSorting = true;
+ /**
+ * @var array the HTML attributes for the link tag in the header cell
+ * generated by [[\yii\data\Sort::link]] when sorting is enabled for this column.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $sortLinkOptions = [];
+ /**
+ * @var string|array|boolean the HTML code representing a filter input (e.g. a text field, a dropdown list)
+ * that is used for this data column. This property is effective only when [[GridView::filterModel]] is set.
+ *
+ * - If this property is not set, a text field will be generated as the filter input;
+ * - If this property is an array, a dropdown list will be generated that uses this property value as
+ * the list options.
+ * - If you don't want a filter for this data column, set this value to be false.
+ */
+ public $filter;
+ /**
+ * @var array the HTML attributes for the filter input fields. This property is used in combination with
+ * the [[filter]] property. When [[filter]] is not set or is an array, this property will be used to
+ * render the HTML attributes for the generated filter input fields.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $filterInputOptions = ['class' => 'form-control', 'id' => null];
- /**
- * @inheritdoc
- */
- protected function renderHeaderCellContent()
- {
- if ($this->header !== null || $this->label === null && $this->attribute === null) {
- return parent::renderHeaderCellContent();
- }
+ /**
+ * @inheritdoc
+ */
+ protected function renderHeaderCellContent()
+ {
+ if ($this->header !== null || $this->label === null && $this->attribute === null) {
+ return parent::renderHeaderCellContent();
+ }
- $provider = $this->grid->dataProvider;
+ $provider = $this->grid->dataProvider;
- if ($this->label === null) {
- if ($provider instanceof ActiveDataProvider && $provider->query instanceof ActiveQueryInterface) {
- /** @var Model $model */
- $model = new $provider->query->modelClass;
- $label = $model->getAttributeLabel($this->attribute);
- } else {
- $models = $provider->getModels();
- if (($model = reset($models)) instanceof Model) {
- /** @var Model $model */
- $label = $model->getAttributeLabel($this->attribute);
- } else {
- $label = Inflector::camel2words($this->attribute);
- }
- }
- } else {
- $label = $this->label;
- }
+ if ($this->label === null) {
+ if ($provider instanceof ActiveDataProvider && $provider->query instanceof ActiveQueryInterface) {
+ /** @var Model $model */
+ $model = new $provider->query->modelClass;
+ $label = $model->getAttributeLabel($this->attribute);
+ } else {
+ $models = $provider->getModels();
+ if (($model = reset($models)) instanceof Model) {
+ /** @var Model $model */
+ $label = $model->getAttributeLabel($this->attribute);
+ } else {
+ $label = Inflector::camel2words($this->attribute);
+ }
+ }
+ } else {
+ $label = $this->label;
+ }
- if ($this->attribute !== null && $this->enableSorting &&
- ($sort = $provider->getSort()) !== false && $sort->hasAttribute($this->attribute)) {
+ if ($this->attribute !== null && $this->enableSorting &&
+ ($sort = $provider->getSort()) !== false && $sort->hasAttribute($this->attribute)) {
+ return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => Html::encode($label)]));
+ } else {
+ return Html::encode($label);
+ }
+ }
- return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => Html::encode($label)]));
- } else {
- return Html::encode($label);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function renderFilterCellContent()
+ {
+ if (is_string($this->filter)) {
+ return $this->filter;
+ } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model &&
+ $this->attribute !== null && $this->grid->filterModel->isAttributeActive($this->attribute))
+ {
+ if (is_array($this->filter)) {
+ $options = array_merge(['prompt' => ''], $this->filterInputOptions);
- /**
- * @inheritdoc
- */
- protected function renderFilterCellContent()
- {
- if (is_string($this->filter)) {
- return $this->filter;
- } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model &&
- $this->attribute !== null && $this->grid->filterModel->isAttributeActive($this->attribute))
- {
- if (is_array($this->filter)) {
- $options = array_merge(['prompt' => ''], $this->filterInputOptions);
- return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, $options);
- } else {
- return Html::activeTextInput($this->grid->filterModel, $this->attribute, $this->filterInputOptions);
- }
- } else {
- return parent::renderFilterCellContent();
- }
- }
+ return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, $options);
+ } else {
+ return Html::activeTextInput($this->grid->filterModel, $this->attribute, $this->filterInputOptions);
+ }
+ } else {
+ return parent::renderFilterCellContent();
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function getDataCellContent($model, $key, $index)
- {
- if ($this->value !== null) {
- if (is_string($this->value)) {
- $value = ArrayHelper::getValue($model, $this->value);
- } else {
- $value = call_user_func($this->value, $model, $index, $this);
- }
- } elseif ($this->content === null && $this->attribute !== null) {
- $value = ArrayHelper::getValue($model, $this->attribute);
- } else {
- return parent::getDataCellContent($model, $key, $index);
- }
- return $value;
- }
+ /**
+ * @inheritdoc
+ */
+ protected function getDataCellContent($model, $key, $index)
+ {
+ if ($this->value !== null) {
+ if (is_string($this->value)) {
+ $value = ArrayHelper::getValue($model, $this->value);
+ } else {
+ $value = call_user_func($this->value, $model, $index, $this);
+ }
+ } elseif ($this->content === null && $this->attribute !== null) {
+ $value = ArrayHelper::getValue($model, $this->attribute);
+ } else {
+ return parent::getDataCellContent($model, $key, $index);
+ }
- /**
- * @inheritdoc
- */
- protected function renderDataCellContent($model, $key, $index)
- {
- return $this->grid->formatter->format($this->getDataCellContent($model, $key, $index), $this->format);
- }
+ return $value;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function renderDataCellContent($model, $key, $index)
+ {
+ return $this->grid->formatter->format($this->getDataCellContent($model, $key, $index), $this->format);
+ }
}
diff --git a/framework/grid/GridView.php b/framework/grid/GridView.php
index e287f8f6f7a..00964926022 100644
--- a/framework/grid/GridView.php
+++ b/framework/grid/GridView.php
@@ -26,440 +26,447 @@
*/
class GridView extends BaseListView
{
- const FILTER_POS_HEADER = 'header';
- const FILTER_POS_FOOTER = 'footer';
- const FILTER_POS_BODY = 'body';
-
- /**
- * @var string the default data column class if the class name is not explicitly specified when configuring a data column.
- * Defaults to 'yii\grid\DataColumn'.
- */
- public $dataColumnClass;
- /**
- * @var string the caption of the grid table
- * @see captionOptions
- */
- public $caption;
- /**
- * @var array the HTML attributes for the caption element.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- * @see caption
- */
- public $captionOptions = [];
- /**
- * @var array the HTML attributes for the grid table element.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $tableOptions = ['class' => 'table table-striped table-bordered'];
- /**
- * @var array the HTML attributes for the container tag of the grid view.
- * The "tag" element specifies the tag name of the container element and defaults to "div".
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'grid-view'];
- /**
- * @var array the HTML attributes for the table header row.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $headerRowOptions = [];
- /**
- * @var array the HTML attributes for the table footer row.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $footerRowOptions = [];
- /**
- * @var array|Closure the HTML attributes for the table body rows. This can be either an array
- * specifying the common HTML attributes for all body rows, or an anonymous function that
- * returns an array of the HTML attributes. The anonymous function will be called once for every
- * data model returned by [[dataProvider]]. It should have the following signature:
- *
- * ```php
- * function ($model, $key, $index, $grid)
- * ```
- *
- * - `$model`: the current data model being rendered
- * - `$key`: the key value associated with the current data model
- * - `$index`: the zero-based index of the data model in the model array returned by [[dataProvider]]
- * - `$grid`: the GridView object
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $rowOptions = [];
- /**
- * @var Closure an anonymous function that is called once BEFORE rendering each data model.
- * It should have the similar signature as [[rowOptions]]. The return result of the function
- * will be rendered directly.
- */
- public $beforeRow;
- /**
- * @var Closure an anonymous function that is called once AFTER rendering each data model.
- * It should have the similar signature as [[rowOptions]]. The return result of the function
- * will be rendered directly.
- */
- public $afterRow;
- /**
- * @var boolean whether to show the header section of the grid table.
- */
- public $showHeader = true;
- /**
- * @var boolean whether to show the footer section of the grid table.
- */
- public $showFooter = false;
- /**
- * @var boolean whether to show the grid view if [[dataProvider]] returns no data.
- */
- public $showOnEmpty = true;
- /**
- * @var array|Formatter the formatter used to format model attribute values into displayable texts.
- * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
- * instance. If this property is not set, the "formatter" application component will be used.
- */
- public $formatter;
- /**
- * @var array grid column configuration. Each array element represents the configuration
- * for one particular grid column. For example,
- *
- * ```php
- * [
- * ['class' => SerialColumn::className()],
- * [
- * 'class' => DataColumn::className(),
- * 'attribute' => 'name',
- * 'format' => 'text',
- * 'label' => 'Name',
- * ],
- * ['class' => CheckboxColumn::className()],
- * ]
- * ```
- *
- * If a column is of class [[DataColumn]], the "class" element can be omitted.
- *
- * As a shortcut format, a string may be used to specify the configuration of a data column
- * which only contains "attribute", "format", and/or "label" options: `"attribute:format:label"`.
- * For example, the above "name" column can also be specified as: `"name:text:Name"`.
- * Both "format" and "label" are optional. They will take default values if absent.
- */
- public $columns = [];
- public $emptyCell = ' ';
- /**
- * @var \yii\base\Model the model that keeps the user-entered filter data. When this property is set,
- * the grid view will enable column-based filtering. Each data column by default will display a text field
- * at the top that users can fill in to filter the data.
- *
- * Note that in order to show an input field for filtering, a column must have its [[DataColumn::attribute]]
- * property set or have [[DataColumn::filter]] set as the HTML code for the input field.
- *
- * When this property is not set (null) the filtering feature is disabled.
- */
- public $filterModel;
- /**
- * @var string|array the URL for returning the filtering result. [[Url::to()]] will be called to
- * normalize the URL. If not set, the current controller action will be used.
- * When the user makes change to any filter input, the current filtering inputs will be appended
- * as GET parameters to this URL.
- */
- public $filterUrl;
- public $filterSelector;
- /**
- * @var string whether the filters should be displayed in the grid view. Valid values include:
- *
- * - [[FILTER_POS_HEADER]]: the filters will be displayed on top of each column's header cell.
- * - [[FILTER_POS_BODY]]: the filters will be displayed right below each column's header cell.
- * - [[FILTER_POS_FOOTER]]: the filters will be displayed below each column's footer cell.
- */
- public $filterPosition = self::FILTER_POS_BODY;
- /**
- * @var array the HTML attributes for the filter row element.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $filterRowOptions = ['class' => 'filters'];
-
-
- /**
- * Initializes the grid view.
- * This method will initialize required property values and instantiate [[columns]] objects.
- */
- public function init()
- {
- parent::init();
- if ($this->formatter == null) {
- $this->formatter = Yii::$app->getFormatter();
- } elseif (is_array($this->formatter)) {
- $this->formatter = Yii::createObject($this->formatter);
- }
- if (!$this->formatter instanceof Formatter) {
- throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
- }
- if (!isset($this->options['id'])) {
- $this->options['id'] = $this->getId();
- }
- if (!isset($this->filterRowOptions['id'])) {
- $this->filterRowOptions['id'] = $this->options['id'] . '-filters';
- }
-
- $this->initColumns();
- }
-
- /**
- * Runs the widget.
- */
- public function run()
- {
- $id = $this->options['id'];
- $options = Json::encode($this->getClientOptions());
- $view = $this->getView();
- GridViewAsset::register($view);
- $view->registerJs("jQuery('#$id').yiiGridView($options);");
- parent::run();
- }
-
- /**
- * Returns the options for the grid view JS widget.
- * @return array the options
- */
- protected function getClientOptions()
- {
- $filterUrl = isset($this->filterUrl) ? $this->filterUrl : [Yii::$app->controller->action->id];
- $id = $this->filterRowOptions['id'];
- $filterSelector = "#$id input, #$id select";
- if (isset($this->filterSelector)) {
- $filterSelector .= ', ' . $this->filterSelector;
- }
-
- return [
- 'filterUrl' => Url::to($filterUrl),
- 'filterSelector' => $filterSelector,
- ];
- }
-
- /**
- * Renders the data models for the grid view.
- */
- public function renderItems()
- {
- $content = array_filter([
- $this->renderCaption(),
- $this->renderColumnGroup(),
- $this->showHeader ? $this->renderTableHeader() : false,
- $this->showFooter ? $this->renderTableFooter() : false,
- $this->renderTableBody(),
- ]);
- return Html::tag('table', implode("\n", $content), $this->tableOptions);
- }
-
- /**
- * Renders the caption element.
- * @return bool|string the rendered caption element or `false` if no caption element should be rendered.
- */
- public function renderCaption()
- {
- if (!empty($this->caption)) {
- return Html::tag('caption', $this->caption, $this->captionOptions);
- } else {
- return false;
- }
- }
-
- /**
- * Renders the column group HTML.
- * @return bool|string the column group HTML or `false` if no column group should be rendered.
- */
- public function renderColumnGroup()
- {
- $requireColumnGroup = false;
- foreach ($this->columns as $column) {
- /** @var Column $column */
- if (!empty($column->options)) {
- $requireColumnGroup = true;
- break;
- }
- }
- if ($requireColumnGroup) {
- $cols = [];
- foreach ($this->columns as $column) {
- $cols[] = Html::tag('col', '', $column->options);
- }
- return Html::tag('colgroup', implode("\n", $cols));
- } else {
- return false;
- }
- }
-
- /**
- * Renders the table header.
- * @return string the rendering result.
- */
- public function renderTableHeader()
- {
- $cells = [];
- foreach ($this->columns as $column) {
- /** @var Column $column */
- $cells[] = $column->renderHeaderCell();
- }
- $content = Html::tag('tr', implode('', $cells), $this->headerRowOptions);
- if ($this->filterPosition == self::FILTER_POS_HEADER) {
- $content = $this->renderFilters() . $content;
- } elseif ($this->filterPosition == self::FILTER_POS_BODY) {
- $content .= $this->renderFilters();
- }
-
- return "\n" . $content . "\n";
- }
-
- /**
- * Renders the table footer.
- * @return string the rendering result.
- */
- public function renderTableFooter()
- {
- $cells = [];
- foreach ($this->columns as $column) {
- /** @var Column $column */
- $cells[] = $column->renderFooterCell();
- }
- $content = Html::tag('tr', implode('', $cells), $this->footerRowOptions);
- if ($this->filterPosition == self::FILTER_POS_FOOTER) {
- $content .= $this->renderFilters();
- }
- return "\n" . $content . "\n";
- }
-
- /**
- * Renders the filter.
- * @return string the rendering result.
- */
- public function renderFilters()
- {
- if ($this->filterModel !== null) {
- $cells = [];
- foreach ($this->columns as $column) {
- /** @var Column $column */
- $cells[] = $column->renderFilterCell();
- }
- return Html::tag('tr', implode('', $cells), $this->filterRowOptions);
- } else {
- return '';
- }
- }
-
- /**
- * Renders the table body.
- * @return string the rendering result.
- */
- public function renderTableBody()
- {
- $models = array_values($this->dataProvider->getModels());
- $keys = $this->dataProvider->getKeys();
- $rows = [];
- foreach ($models as $index => $model) {
- $key = $keys[$index];
- if ($this->beforeRow !== null) {
- $row = call_user_func($this->beforeRow, $model, $key, $index, $this);
- if (!empty($row)) {
- $rows[] = $row;
- }
- }
-
- $rows[] = $this->renderTableRow($model, $key, $index);
-
- if ($this->afterRow !== null) {
- $row = call_user_func($this->afterRow, $model, $key, $index, $this);
- if (!empty($row)) {
- $rows[] = $row;
- }
- }
- }
-
- if (empty($rows)) {
- $colspan = count($this->columns);
- return "\n
" . $this->renderEmpty() . "
\n";
- } else {
- return "\n" . implode("\n", $rows) . "\n";
- }
- }
-
- /**
- * Renders a table row with the given data model and key.
- * @param mixed $model the data model to be rendered
- * @param mixed $key the key associated with the data model
- * @param integer $index the zero-based index of the data model among the model array returned by [[dataProvider]].
- * @return string the rendering result
- */
- public function renderTableRow($model, $key, $index)
- {
- $cells = [];
- /** @var Column $column */
- foreach ($this->columns as $column) {
- $cells[] = $column->renderDataCell($model, $key, $index);
- }
- if ($this->rowOptions instanceof Closure) {
- $options = call_user_func($this->rowOptions, $model, $key, $index, $this);
- } else {
- $options = $this->rowOptions;
- }
- $options['data-key'] = is_array($key) ? json_encode($key) : (string)$key;
- return Html::tag('tr', implode('', $cells), $options);
- }
-
- /**
- * Creates column objects and initializes them.
- */
- protected function initColumns()
- {
- if (empty($this->columns)) {
- $this->guessColumns();
- }
- foreach ($this->columns as $i => $column) {
- if (is_string($column)) {
- $column = $this->createDataColumn($column);
- } else {
- $column = Yii::createObject(array_merge([
- 'class' => $this->dataColumnClass ?: DataColumn::className(),
- 'grid' => $this,
- ], $column));
- }
- if (!$column->visible) {
- unset($this->columns[$i]);
- continue;
- }
- $this->columns[$i] = $column;
- }
- }
-
- /**
- * Creates a [[DataColumn]] object based on a string in the format of "attribute:format:label".
- * @param string $text the column specification string
- * @return DataColumn the column instance
- * @throws InvalidConfigException if the column specification is invalid
- */
- protected function createDataColumn($text)
- {
- if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $text, $matches)) {
- throw new InvalidConfigException('The column must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"');
- }
- return Yii::createObject([
- 'class' => $this->dataColumnClass ?: DataColumn::className(),
- 'grid' => $this,
- 'attribute' => $matches[1],
- 'format' => isset($matches[3]) ? $matches[3] : 'text',
- 'label' => isset($matches[5]) ? $matches[5] : null,
- ]);
- }
-
- /**
- * This function tries to guesses the columns to show from the given data
- * if [[columns]] are not explicitly specified.
- */
- protected function guessColumns()
- {
- $models = $this->dataProvider->getModels();
- $model = reset($models);
- if (is_array($model) || is_object($model)) {
- foreach ($model as $name => $value) {
- $this->columns[] = $name;
- }
- } else {
- throw new InvalidConfigException('Unable to generate columns from data.');
- }
- }
+ const FILTER_POS_HEADER = 'header';
+ const FILTER_POS_FOOTER = 'footer';
+ const FILTER_POS_BODY = 'body';
+
+ /**
+ * @var string the default data column class if the class name is not explicitly specified when configuring a data column.
+ * Defaults to 'yii\grid\DataColumn'.
+ */
+ public $dataColumnClass;
+ /**
+ * @var string the caption of the grid table
+ * @see captionOptions
+ */
+ public $caption;
+ /**
+ * @var array the HTML attributes for the caption element.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ * @see caption
+ */
+ public $captionOptions = [];
+ /**
+ * @var array the HTML attributes for the grid table element.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $tableOptions = ['class' => 'table table-striped table-bordered'];
+ /**
+ * @var array the HTML attributes for the container tag of the grid view.
+ * The "tag" element specifies the tag name of the container element and defaults to "div".
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'grid-view'];
+ /**
+ * @var array the HTML attributes for the table header row.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $headerRowOptions = [];
+ /**
+ * @var array the HTML attributes for the table footer row.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $footerRowOptions = [];
+ /**
+ * @var array|Closure the HTML attributes for the table body rows. This can be either an array
+ * specifying the common HTML attributes for all body rows, or an anonymous function that
+ * returns an array of the HTML attributes. The anonymous function will be called once for every
+ * data model returned by [[dataProvider]]. It should have the following signature:
+ *
+ * ```php
+ * function ($model, $key, $index, $grid)
+ * ```
+ *
+ * - `$model`: the current data model being rendered
+ * - `$key`: the key value associated with the current data model
+ * - `$index`: the zero-based index of the data model in the model array returned by [[dataProvider]]
+ * - `$grid`: the GridView object
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $rowOptions = [];
+ /**
+ * @var Closure an anonymous function that is called once BEFORE rendering each data model.
+ * It should have the similar signature as [[rowOptions]]. The return result of the function
+ * will be rendered directly.
+ */
+ public $beforeRow;
+ /**
+ * @var Closure an anonymous function that is called once AFTER rendering each data model.
+ * It should have the similar signature as [[rowOptions]]. The return result of the function
+ * will be rendered directly.
+ */
+ public $afterRow;
+ /**
+ * @var boolean whether to show the header section of the grid table.
+ */
+ public $showHeader = true;
+ /**
+ * @var boolean whether to show the footer section of the grid table.
+ */
+ public $showFooter = false;
+ /**
+ * @var boolean whether to show the grid view if [[dataProvider]] returns no data.
+ */
+ public $showOnEmpty = true;
+ /**
+ * @var array|Formatter the formatter used to format model attribute values into displayable texts.
+ * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
+ * instance. If this property is not set, the "formatter" application component will be used.
+ */
+ public $formatter;
+ /**
+ * @var array grid column configuration. Each array element represents the configuration
+ * for one particular grid column. For example,
+ *
+ * ```php
+ * [
+ * ['class' => SerialColumn::className()],
+ * [
+ * 'class' => DataColumn::className(),
+ * 'attribute' => 'name',
+ * 'format' => 'text',
+ * 'label' => 'Name',
+ * ],
+ * ['class' => CheckboxColumn::className()],
+ * ]
+ * ```
+ *
+ * If a column is of class [[DataColumn]], the "class" element can be omitted.
+ *
+ * As a shortcut format, a string may be used to specify the configuration of a data column
+ * which only contains "attribute", "format", and/or "label" options: `"attribute:format:label"`.
+ * For example, the above "name" column can also be specified as: `"name:text:Name"`.
+ * Both "format" and "label" are optional. They will take default values if absent.
+ */
+ public $columns = [];
+ public $emptyCell = ' ';
+ /**
+ * @var \yii\base\Model the model that keeps the user-entered filter data. When this property is set,
+ * the grid view will enable column-based filtering. Each data column by default will display a text field
+ * at the top that users can fill in to filter the data.
+ *
+ * Note that in order to show an input field for filtering, a column must have its [[DataColumn::attribute]]
+ * property set or have [[DataColumn::filter]] set as the HTML code for the input field.
+ *
+ * When this property is not set (null) the filtering feature is disabled.
+ */
+ public $filterModel;
+ /**
+ * @var string|array the URL for returning the filtering result. [[Url::to()]] will be called to
+ * normalize the URL. If not set, the current controller action will be used.
+ * When the user makes change to any filter input, the current filtering inputs will be appended
+ * as GET parameters to this URL.
+ */
+ public $filterUrl;
+ public $filterSelector;
+ /**
+ * @var string whether the filters should be displayed in the grid view. Valid values include:
+ *
+ * - [[FILTER_POS_HEADER]]: the filters will be displayed on top of each column's header cell.
+ * - [[FILTER_POS_BODY]]: the filters will be displayed right below each column's header cell.
+ * - [[FILTER_POS_FOOTER]]: the filters will be displayed below each column's footer cell.
+ */
+ public $filterPosition = self::FILTER_POS_BODY;
+ /**
+ * @var array the HTML attributes for the filter row element.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $filterRowOptions = ['class' => 'filters'];
+
+
+ /**
+ * Initializes the grid view.
+ * This method will initialize required property values and instantiate [[columns]] objects.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->formatter == null) {
+ $this->formatter = Yii::$app->getFormatter();
+ } elseif (is_array($this->formatter)) {
+ $this->formatter = Yii::createObject($this->formatter);
+ }
+ if (!$this->formatter instanceof Formatter) {
+ throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
+ }
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->getId();
+ }
+ if (!isset($this->filterRowOptions['id'])) {
+ $this->filterRowOptions['id'] = $this->options['id'] . '-filters';
+ }
+
+ $this->initColumns();
+ }
+
+ /**
+ * Runs the widget.
+ */
+ public function run()
+ {
+ $id = $this->options['id'];
+ $options = Json::encode($this->getClientOptions());
+ $view = $this->getView();
+ GridViewAsset::register($view);
+ $view->registerJs("jQuery('#$id').yiiGridView($options);");
+ parent::run();
+ }
+
+ /**
+ * Returns the options for the grid view JS widget.
+ * @return array the options
+ */
+ protected function getClientOptions()
+ {
+ $filterUrl = isset($this->filterUrl) ? $this->filterUrl : [Yii::$app->controller->action->id];
+ $id = $this->filterRowOptions['id'];
+ $filterSelector = "#$id input, #$id select";
+ if (isset($this->filterSelector)) {
+ $filterSelector .= ', ' . $this->filterSelector;
+ }
+
+ return [
+ 'filterUrl' => Url::to($filterUrl),
+ 'filterSelector' => $filterSelector,
+ ];
+ }
+
+ /**
+ * Renders the data models for the grid view.
+ */
+ public function renderItems()
+ {
+ $content = array_filter([
+ $this->renderCaption(),
+ $this->renderColumnGroup(),
+ $this->showHeader ? $this->renderTableHeader() : false,
+ $this->showFooter ? $this->renderTableFooter() : false,
+ $this->renderTableBody(),
+ ]);
+
+ return Html::tag('table', implode("\n", $content), $this->tableOptions);
+ }
+
+ /**
+ * Renders the caption element.
+ * @return bool|string the rendered caption element or `false` if no caption element should be rendered.
+ */
+ public function renderCaption()
+ {
+ if (!empty($this->caption)) {
+ return Html::tag('caption', $this->caption, $this->captionOptions);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Renders the column group HTML.
+ * @return bool|string the column group HTML or `false` if no column group should be rendered.
+ */
+ public function renderColumnGroup()
+ {
+ $requireColumnGroup = false;
+ foreach ($this->columns as $column) {
+ /** @var Column $column */
+ if (!empty($column->options)) {
+ $requireColumnGroup = true;
+ break;
+ }
+ }
+ if ($requireColumnGroup) {
+ $cols = [];
+ foreach ($this->columns as $column) {
+ $cols[] = Html::tag('col', '', $column->options);
+ }
+
+ return Html::tag('colgroup', implode("\n", $cols));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Renders the table header.
+ * @return string the rendering result.
+ */
+ public function renderTableHeader()
+ {
+ $cells = [];
+ foreach ($this->columns as $column) {
+ /** @var Column $column */
+ $cells[] = $column->renderHeaderCell();
+ }
+ $content = Html::tag('tr', implode('', $cells), $this->headerRowOptions);
+ if ($this->filterPosition == self::FILTER_POS_HEADER) {
+ $content = $this->renderFilters() . $content;
+ } elseif ($this->filterPosition == self::FILTER_POS_BODY) {
+ $content .= $this->renderFilters();
+ }
+
+ return "\n" . $content . "\n";
+ }
+
+ /**
+ * Renders the table footer.
+ * @return string the rendering result.
+ */
+ public function renderTableFooter()
+ {
+ $cells = [];
+ foreach ($this->columns as $column) {
+ /** @var Column $column */
+ $cells[] = $column->renderFooterCell();
+ }
+ $content = Html::tag('tr', implode('', $cells), $this->footerRowOptions);
+ if ($this->filterPosition == self::FILTER_POS_FOOTER) {
+ $content .= $this->renderFilters();
+ }
+
+ return "\n" . $content . "\n";
+ }
+
+ /**
+ * Renders the filter.
+ * @return string the rendering result.
+ */
+ public function renderFilters()
+ {
+ if ($this->filterModel !== null) {
+ $cells = [];
+ foreach ($this->columns as $column) {
+ /** @var Column $column */
+ $cells[] = $column->renderFilterCell();
+ }
+
+ return Html::tag('tr', implode('', $cells), $this->filterRowOptions);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Renders the table body.
+ * @return string the rendering result.
+ */
+ public function renderTableBody()
+ {
+ $models = array_values($this->dataProvider->getModels());
+ $keys = $this->dataProvider->getKeys();
+ $rows = [];
+ foreach ($models as $index => $model) {
+ $key = $keys[$index];
+ if ($this->beforeRow !== null) {
+ $row = call_user_func($this->beforeRow, $model, $key, $index, $this);
+ if (!empty($row)) {
+ $rows[] = $row;
+ }
+ }
+
+ $rows[] = $this->renderTableRow($model, $key, $index);
+
+ if ($this->afterRow !== null) {
+ $row = call_user_func($this->afterRow, $model, $key, $index, $this);
+ if (!empty($row)) {
+ $rows[] = $row;
+ }
+ }
+ }
+
+ if (empty($rows)) {
+ $colspan = count($this->columns);
+
+ return "\n
" . $this->renderEmpty() . "
\n";
+ } else {
+ return "\n" . implode("\n", $rows) . "\n";
+ }
+ }
+
+ /**
+ * Renders a table row with the given data model and key.
+ * @param mixed $model the data model to be rendered
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data model among the model array returned by [[dataProvider]].
+ * @return string the rendering result
+ */
+ public function renderTableRow($model, $key, $index)
+ {
+ $cells = [];
+ /** @var Column $column */
+ foreach ($this->columns as $column) {
+ $cells[] = $column->renderDataCell($model, $key, $index);
+ }
+ if ($this->rowOptions instanceof Closure) {
+ $options = call_user_func($this->rowOptions, $model, $key, $index, $this);
+ } else {
+ $options = $this->rowOptions;
+ }
+ $options['data-key'] = is_array($key) ? json_encode($key) : (string) $key;
+
+ return Html::tag('tr', implode('', $cells), $options);
+ }
+
+ /**
+ * Creates column objects and initializes them.
+ */
+ protected function initColumns()
+ {
+ if (empty($this->columns)) {
+ $this->guessColumns();
+ }
+ foreach ($this->columns as $i => $column) {
+ if (is_string($column)) {
+ $column = $this->createDataColumn($column);
+ } else {
+ $column = Yii::createObject(array_merge([
+ 'class' => $this->dataColumnClass ?: DataColumn::className(),
+ 'grid' => $this,
+ ], $column));
+ }
+ if (!$column->visible) {
+ unset($this->columns[$i]);
+ continue;
+ }
+ $this->columns[$i] = $column;
+ }
+ }
+
+ /**
+ * Creates a [[DataColumn]] object based on a string in the format of "attribute:format:label".
+ * @param string $text the column specification string
+ * @return DataColumn the column instance
+ * @throws InvalidConfigException if the column specification is invalid
+ */
+ protected function createDataColumn($text)
+ {
+ if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $text, $matches)) {
+ throw new InvalidConfigException('The column must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"');
+ }
+
+ return Yii::createObject([
+ 'class' => $this->dataColumnClass ?: DataColumn::className(),
+ 'grid' => $this,
+ 'attribute' => $matches[1],
+ 'format' => isset($matches[3]) ? $matches[3] : 'text',
+ 'label' => isset($matches[5]) ? $matches[5] : null,
+ ]);
+ }
+
+ /**
+ * This function tries to guesses the columns to show from the given data
+ * if [[columns]] are not explicitly specified.
+ */
+ protected function guessColumns()
+ {
+ $models = $this->dataProvider->getModels();
+ $model = reset($models);
+ if (is_array($model) || is_object($model)) {
+ foreach ($model as $name => $value) {
+ $this->columns[] = $name;
+ }
+ } else {
+ throw new InvalidConfigException('Unable to generate columns from data.');
+ }
+ }
}
diff --git a/framework/grid/GridViewAsset.php b/framework/grid/GridViewAsset.php
index a67999d3104..0403e9f5b76 100644
--- a/framework/grid/GridViewAsset.php
+++ b/framework/grid/GridViewAsset.php
@@ -17,11 +17,11 @@
*/
class GridViewAsset extends AssetBundle
{
- public $sourcePath = '@yii/assets';
- public $js = [
- 'yii.gridView.js',
- ];
- public $depends = [
- 'yii\web\YiiAsset',
- ];
+ public $sourcePath = '@yii/assets';
+ public $js = [
+ 'yii.gridView.js',
+ ];
+ public $depends = [
+ 'yii\web\YiiAsset',
+ ];
}
diff --git a/framework/grid/SerialColumn.php b/framework/grid/SerialColumn.php
index e5dc74cbc92..50a3f6bba9b 100644
--- a/framework/grid/SerialColumn.php
+++ b/framework/grid/SerialColumn.php
@@ -27,18 +27,18 @@
*/
class SerialColumn extends Column
{
- public $header = '#';
+ public $header = '#';
- /**
- * @inheritdoc
- */
- protected function renderDataCellContent($model, $key, $index)
- {
- $pagination = $this->grid->dataProvider->getPagination();
- if ($pagination !== false) {
- return $pagination->getOffset() + $index + 1;
- } else {
- return $index + 1;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function renderDataCellContent($model, $key, $index)
+ {
+ $pagination = $this->grid->dataProvider->getPagination();
+ if ($pagination !== false) {
+ return $pagination->getOffset() + $index + 1;
+ } else {
+ return $index + 1;
+ }
+ }
}
diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php
index 278eaf096c6..d0b1a8f5cab 100644
--- a/framework/helpers/BaseArrayHelper.php
+++ b/framework/helpers/BaseArrayHelper.php
@@ -21,462 +21,473 @@
*/
class BaseArrayHelper
{
- /**
- * Converts an object or an array of objects into an array.
- * @param object|array $object the object to be converted into an array
- * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays.
- * The properties specified for each class is an array of the following format:
- *
- * ~~~
- * [
- * 'app\models\Post' => [
- * 'id',
- * 'title',
- * // the key name in array result => property name
- * 'createTime' => 'created_at',
- * // the key name in array result => anonymous function
- * 'length' => function ($post) {
- * return strlen($post->content);
- * },
- * ],
- * ]
- * ~~~
- *
- * The result of `ArrayHelper::toArray($post, $properties)` could be like the following:
- *
- * ~~~
- * [
- * 'id' => 123,
- * 'title' => 'test',
- * 'createTime' => '2013-01-01 12:00AM',
- * 'length' => 301,
- * ]
- * ~~~
- *
- * @param boolean $recursive whether to recursively converts properties which are objects into arrays.
- * @return array the array representation of the object
- */
- public static function toArray($object, $properties = [], $recursive = true)
- {
- if (is_array($object)) {
- if ($recursive) {
- foreach ($object as $key => $value) {
- if (is_array($value) || is_object($value)) {
- $object[$key] = static::toArray($value, true);
- }
- }
- }
- return $object;
- } elseif (is_object($object)) {
- if (!empty($properties)) {
- $className = get_class($object);
- if (!empty($properties[$className])) {
- $result = [];
- foreach ($properties[$className] as $key => $name) {
- if (is_int($key)) {
- $result[$name] = $object->$name;
- } else {
- $result[$key] = static::getValue($object, $name);
- }
- }
- return $recursive ? static::toArray($result) : $result;
- }
- }
- if ($object instanceof Arrayable) {
- $result = $object->toArray();
- } else {
- $result = [];
- foreach ($object as $key => $value) {
- $result[$key] = $value;
- }
- }
- return $recursive ? static::toArray($result) : $result;
- } else {
- return [$object];
- }
- }
+ /**
+ * Converts an object or an array of objects into an array.
+ * @param object|array $object the object to be converted into an array
+ * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays.
+ * The properties specified for each class is an array of the following format:
+ *
+ * ~~~
+ * [
+ * 'app\models\Post' => [
+ * 'id',
+ * 'title',
+ * // the key name in array result => property name
+ * 'createTime' => 'created_at',
+ * // the key name in array result => anonymous function
+ * 'length' => function ($post) {
+ * return strlen($post->content);
+ * },
+ * ],
+ * ]
+ * ~~~
+ *
+ * The result of `ArrayHelper::toArray($post, $properties)` could be like the following:
+ *
+ * ~~~
+ * [
+ * 'id' => 123,
+ * 'title' => 'test',
+ * 'createTime' => '2013-01-01 12:00AM',
+ * 'length' => 301,
+ * ]
+ * ~~~
+ *
+ * @param boolean $recursive whether to recursively converts properties which are objects into arrays.
+ * @return array the array representation of the object
+ */
+ public static function toArray($object, $properties = [], $recursive = true)
+ {
+ if (is_array($object)) {
+ if ($recursive) {
+ foreach ($object as $key => $value) {
+ if (is_array($value) || is_object($value)) {
+ $object[$key] = static::toArray($value, true);
+ }
+ }
+ }
- /**
- * Merges two or more arrays into one recursively.
- * If each array has an element with the same string key value, the latter
- * will overwrite the former (different from array_merge_recursive).
- * Recursive merging will be conducted if both arrays have an element of array
- * type and are having the same key.
- * For integer-keyed elements, the elements from the latter array will
- * be appended to the former array.
- * @param array $a array to be merged to
- * @param array $b array to be merged from. You can specify additional
- * arrays via third argument, fourth argument etc.
- * @return array the merged array (the original arrays are not changed.)
- */
- public static function merge($a, $b)
- {
- $args = func_get_args();
- $res = array_shift($args);
- while (!empty($args)) {
- $next = array_shift($args);
- foreach ($next as $k => $v) {
- if (is_integer($k)) {
- isset($res[$k]) ? $res[] = $v : $res[$k] = $v;
- } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
- $res[$k] = self::merge($res[$k], $v);
- } else {
- $res[$k] = $v;
- }
- }
- }
- return $res;
- }
+ return $object;
+ } elseif (is_object($object)) {
+ if (!empty($properties)) {
+ $className = get_class($object);
+ if (!empty($properties[$className])) {
+ $result = [];
+ foreach ($properties[$className] as $key => $name) {
+ if (is_int($key)) {
+ $result[$name] = $object->$name;
+ } else {
+ $result[$key] = static::getValue($object, $name);
+ }
+ }
- /**
- * Retrieves the value of an array element or object property with the given key or property name.
- * If the key does not exist in the array or object, the default value will be returned instead.
- *
- * The key may be specified in a dot format to retrieve the value of a sub-array or the property
- * of an embedded object. In particular, if the key is `x.y.z`, then the returned value would
- * be `$array['x']['y']['z']` or `$array->x->y->z` (if `$array` is an object). If `$array['x']`
- * or `$array->x` is neither an array nor an object, the default value will be returned.
- * Note that if the array already has an element `x.y.z`, then its value will be returned
- * instead of going through the sub-arrays.
- *
- * Below are some usage examples,
- *
- * ~~~
- * // working with array
- * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username');
- * // working with object
- * $username = \yii\helpers\ArrayHelper::getValue($user, 'username');
- * // working with anonymous function
- * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) {
- * return $user->firstName . ' ' . $user->lastName;
- * });
- * // using dot format to retrieve the property of embedded object
- * $street = \yii\helpers\ArrayHelper::getValue($users, 'address.street');
- * ~~~
- *
- * @param array|object $array array or object to extract value from
- * @param string|\Closure $key key name of the array element, or property name of the object,
- * or an anonymous function returning the value. The anonymous function signature should be:
- * `function($array, $defaultValue)`.
- * @param mixed $default the default value to be returned if the specified key does not exist
- * @return mixed the value of the element if found, default value otherwise
- * @throws InvalidParamException if $array is neither an array nor an object.
- */
- public static function getValue($array, $key, $default = null)
- {
- if ($key instanceof \Closure) {
- return $key($array, $default);
- }
+ return $recursive ? static::toArray($result) : $result;
+ }
+ }
+ if ($object instanceof Arrayable) {
+ $result = $object->toArray();
+ } else {
+ $result = [];
+ foreach ($object as $key => $value) {
+ $result[$key] = $value;
+ }
+ }
- if (is_array($array) && array_key_exists($key, $array)) {
- return $array[$key];
- }
+ return $recursive ? static::toArray($result) : $result;
+ } else {
+ return [$object];
+ }
+ }
- if (($pos = strrpos($key, '.')) !== false) {
- $array = static::getValue($array, substr($key, 0, $pos), $default);
- $key = substr($key, $pos + 1);
- }
+ /**
+ * Merges two or more arrays into one recursively.
+ * If each array has an element with the same string key value, the latter
+ * will overwrite the former (different from array_merge_recursive).
+ * Recursive merging will be conducted if both arrays have an element of array
+ * type and are having the same key.
+ * For integer-keyed elements, the elements from the latter array will
+ * be appended to the former array.
+ * @param array $a array to be merged to
+ * @param array $b array to be merged from. You can specify additional
+ * arrays via third argument, fourth argument etc.
+ * @return array the merged array (the original arrays are not changed.)
+ */
+ public static function merge($a, $b)
+ {
+ $args = func_get_args();
+ $res = array_shift($args);
+ while (!empty($args)) {
+ $next = array_shift($args);
+ foreach ($next as $k => $v) {
+ if (is_integer($k)) {
+ isset($res[$k]) ? $res[] = $v : $res[$k] = $v;
+ } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
+ $res[$k] = self::merge($res[$k], $v);
+ } else {
+ $res[$k] = $v;
+ }
+ }
+ }
- if (is_object($array)) {
- return $array->$key;
- } elseif (is_array($array)) {
- return array_key_exists($key, $array) ? $array[$key] : $default;
- } else {
- return $default;
- }
- }
+ return $res;
+ }
- /**
- * Removes an item from an array and returns the value. If the key does not exist in the array, the default value
- * will be returned instead.
- *
- * Usage examples,
- *
- * ~~~
- * // $array = ['type' => 'A', 'options' => [1, 2]];
- * // working with array
- * $type = \yii\helpers\ArrayHelper::remove($array, 'type');
- * // $array content
- * // $array = ['options' => [1, 2]];
- * ~~~
- *
- * @param array $array the array to extract value from
- * @param string $key key name of the array element
- * @param mixed $default the default value to be returned if the specified key does not exist
- * @return mixed|null the value of the element if found, default value otherwise
- */
- public static function remove(&$array, $key, $default = null)
- {
- if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
- $value = $array[$key];
- unset($array[$key]);
- return $value;
- }
- return $default;
- }
+ /**
+ * Retrieves the value of an array element or object property with the given key or property name.
+ * If the key does not exist in the array or object, the default value will be returned instead.
+ *
+ * The key may be specified in a dot format to retrieve the value of a sub-array or the property
+ * of an embedded object. In particular, if the key is `x.y.z`, then the returned value would
+ * be `$array['x']['y']['z']` or `$array->x->y->z` (if `$array` is an object). If `$array['x']`
+ * or `$array->x` is neither an array nor an object, the default value will be returned.
+ * Note that if the array already has an element `x.y.z`, then its value will be returned
+ * instead of going through the sub-arrays.
+ *
+ * Below are some usage examples,
+ *
+ * ~~~
+ * // working with array
+ * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username');
+ * // working with object
+ * $username = \yii\helpers\ArrayHelper::getValue($user, 'username');
+ * // working with anonymous function
+ * $fullName = \yii\helpers\ArrayHelper::getValue($user, function ($user, $defaultValue) {
+ * return $user->firstName . ' ' . $user->lastName;
+ * });
+ * // using dot format to retrieve the property of embedded object
+ * $street = \yii\helpers\ArrayHelper::getValue($users, 'address.street');
+ * ~~~
+ *
+ * @param array|object $array array or object to extract value from
+ * @param string|\Closure $key key name of the array element, or property name of the object,
+ * or an anonymous function returning the value. The anonymous function signature should be:
+ * `function($array, $defaultValue)`.
+ * @param mixed $default the default value to be returned if the specified key does not exist
+ * @return mixed the value of the element if found, default value otherwise
+ * @throws InvalidParamException if $array is neither an array nor an object.
+ */
+ public static function getValue($array, $key, $default = null)
+ {
+ if ($key instanceof \Closure) {
+ return $key($array, $default);
+ }
- /**
- * Indexes an array according to a specified key.
- * The input array should be multidimensional or an array of objects.
- *
- * The key can be a key name of the sub-array, a property name of object, or an anonymous
- * function which returns the key value given an array element.
- *
- * If a key value is null, the corresponding array element will be discarded and not put in the result.
- *
- * For example,
- *
- * ~~~
- * $array = [
- * ['id' => '123', 'data' => 'abc'],
- * ['id' => '345', 'data' => 'def'],
- * ];
- * $result = ArrayHelper::index($array, 'id');
- * // the result is:
- * // [
- * // '123' => ['id' => '123', 'data' => 'abc'],
- * // '345' => ['id' => '345', 'data' => 'def'],
- * // ]
- *
- * // using anonymous function
- * $result = ArrayHelper::index($array, function ($element) {
- * return $element['id'];
- * });
- * ~~~
- *
- * @param array $array the array that needs to be indexed
- * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array
- * @return array the indexed array
- */
- public static function index($array, $key)
- {
- $result = [];
- foreach ($array as $element) {
- $value = static::getValue($element, $key);
- $result[$value] = $element;
- }
- return $result;
- }
+ if (is_array($array) && array_key_exists($key, $array)) {
+ return $array[$key];
+ }
- /**
- * Returns the values of a specified column in an array.
- * The input array should be multidimensional or an array of objects.
- *
- * For example,
- *
- * ~~~
- * $array = [
- * ['id' => '123', 'data' => 'abc'],
- * ['id' => '345', 'data' => 'def'],
- * ];
- * $result = ArrayHelper::getColumn($array, 'id');
- * // the result is: ['123', '345']
- *
- * // using anonymous function
- * $result = ArrayHelper::getColumn($array, function ($element) {
- * return $element['id'];
- * });
- * ~~~
- *
- * @param array $array
- * @param string|\Closure $name
- * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array
- * will be re-indexed with integers.
- * @return array the list of column values
- */
- public static function getColumn($array, $name, $keepKeys = true)
- {
- $result = [];
- if ($keepKeys) {
- foreach ($array as $k => $element) {
- $result[$k] = static::getValue($element, $name);
- }
- } else {
- foreach ($array as $element) {
- $result[] = static::getValue($element, $name);
- }
- }
+ if (($pos = strrpos($key, '.')) !== false) {
+ $array = static::getValue($array, substr($key, 0, $pos), $default);
+ $key = substr($key, $pos + 1);
+ }
- return $result;
- }
+ if (is_object($array)) {
+ return $array->$key;
+ } elseif (is_array($array)) {
+ return array_key_exists($key, $array) ? $array[$key] : $default;
+ } else {
+ return $default;
+ }
+ }
- /**
- * Builds a map (key-value pairs) from a multidimensional array or an array of objects.
- * The `$from` and `$to` parameters specify the key names or property names to set up the map.
- * Optionally, one can further group the map according to a grouping field `$group`.
- *
- * For example,
- *
- * ~~~
- * $array = [
- * ['id' => '123', 'name' => 'aaa', 'class' => 'x'],
- * ['id' => '124', 'name' => 'bbb', 'class' => 'x'],
- * ['id' => '345', 'name' => 'ccc', 'class' => 'y'],
- * );
- *
- * $result = ArrayHelper::map($array, 'id', 'name');
- * // the result is:
- * // [
- * // '123' => 'aaa',
- * // '124' => 'bbb',
- * // '345' => 'ccc',
- * // ]
- *
- * $result = ArrayHelper::map($array, 'id', 'name', 'class');
- * // the result is:
- * // [
- * // 'x' => [
- * // '123' => 'aaa',
- * // '124' => 'bbb',
- * // ],
- * // 'y' => [
- * // '345' => 'ccc',
- * // ],
- * // ]
- * ~~~
- *
- * @param array $array
- * @param string|\Closure $from
- * @param string|\Closure $to
- * @param string|\Closure $group
- * @return array
- */
- public static function map($array, $from, $to, $group = null)
- {
- $result = [];
- foreach ($array as $element) {
- $key = static::getValue($element, $from);
- $value = static::getValue($element, $to);
- if ($group !== null) {
- $result[static::getValue($element, $group)][$key] = $value;
- } else {
- $result[$key] = $value;
- }
- }
- return $result;
- }
+ /**
+ * Removes an item from an array and returns the value. If the key does not exist in the array, the default value
+ * will be returned instead.
+ *
+ * Usage examples,
+ *
+ * ~~~
+ * // $array = ['type' => 'A', 'options' => [1, 2]];
+ * // working with array
+ * $type = \yii\helpers\ArrayHelper::remove($array, 'type');
+ * // $array content
+ * // $array = ['options' => [1, 2]];
+ * ~~~
+ *
+ * @param array $array the array to extract value from
+ * @param string $key key name of the array element
+ * @param mixed $default the default value to be returned if the specified key does not exist
+ * @return mixed|null the value of the element if found, default value otherwise
+ */
+ public static function remove(&$array, $key, $default = null)
+ {
+ if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
+ $value = $array[$key];
+ unset($array[$key]);
- /**
- * Checks if the given array contains the specified key.
- * This method enhances the `array_key_exists()` function by supporting case-insensitive
- * key comparison.
- * @param string $key the key to check
- * @param array $array the array with keys to check
- * @param boolean $caseSensitive whether the key comparison should be case-sensitive
- * @return boolean whether the array contains the specified key
- */
- public static function keyExists($key, $array, $caseSensitive = true)
- {
- if ($caseSensitive) {
- return array_key_exists($key, $array);
- } else {
- foreach (array_keys($array) as $k) {
- if (strcasecmp($key, $k) === 0) {
- return true;
- }
- }
- return false;
- }
- }
+ return $value;
+ }
- /**
- * Sorts an array of objects or arrays (with the same structure) by one or several keys.
- * @param array $array the array to be sorted. The array will be modified after calling this method.
- * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
- * elements, a property name of the objects, or an anonymous function returning the values for comparison
- * purpose. The anonymous function signature should be: `function($item)`.
- * To sort by multiple keys, provide an array of keys here.
- * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
- * When sorting by multiple keys with different sorting directions, use an array of sorting directions.
- * @param integer|array $sortFlag the PHP sort flag. Valid values include
- * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`.
- * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php)
- * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags.
- * @throws InvalidParamException if the $descending or $sortFlag parameters do not have
- * correct number of elements as that of $key.
- */
- public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR)
- {
- $keys = is_array($key) ? $key : [$key];
- if (empty($keys) || empty($array)) {
- return;
- }
- $n = count($keys);
- if (is_scalar($direction)) {
- $direction = array_fill(0, $n, $direction);
- } elseif (count($direction) !== $n) {
- throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.');
- }
- if (is_scalar($sortFlag)) {
- $sortFlag = array_fill(0, $n, $sortFlag);
- } elseif (count($sortFlag) !== $n) {
- throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.');
- }
- $args = [];
- foreach ($keys as $i => $key) {
- $flag = $sortFlag[$i];
- $args[] = static::getColumn($array, $key);
- $args[] = $direction[$i];
- $args[] = $flag;
- }
- $args[] = &$array;
- call_user_func_array('array_multisort', $args);
- }
+ return $default;
+ }
- /**
- * Encodes special characters in an array of strings into HTML entities.
- * Both the array keys and values will be encoded.
- * If a value is an array, this method will also encode it recursively.
- * @param array $data data to be encoded
- * @param boolean $valuesOnly whether to encode array values only. If false,
- * both the array keys and array values will be encoded.
- * @param string $charset the charset that the data is using. If not set,
- * [[\yii\base\Application::charset]] will be used.
- * @return array the encoded data
- * @see http://www.php.net/manual/en/function.htmlspecialchars.php
- */
- public static function htmlEncode($data, $valuesOnly = true, $charset = null)
- {
- if ($charset === null) {
- $charset = Yii::$app->charset;
- }
- $d = [];
- foreach ($data as $key => $value) {
- if (!$valuesOnly && is_string($key)) {
- $key = htmlspecialchars($key, ENT_QUOTES, $charset);
- }
- if (is_string($value)) {
- $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset);
- } elseif (is_array($value)) {
- $d[$key] = static::htmlEncode($value, $charset);
- }
- }
- return $d;
- }
+ /**
+ * Indexes an array according to a specified key.
+ * The input array should be multidimensional or an array of objects.
+ *
+ * The key can be a key name of the sub-array, a property name of object, or an anonymous
+ * function which returns the key value given an array element.
+ *
+ * If a key value is null, the corresponding array element will be discarded and not put in the result.
+ *
+ * For example,
+ *
+ * ~~~
+ * $array = [
+ * ['id' => '123', 'data' => 'abc'],
+ * ['id' => '345', 'data' => 'def'],
+ * ];
+ * $result = ArrayHelper::index($array, 'id');
+ * // the result is:
+ * // [
+ * // '123' => ['id' => '123', 'data' => 'abc'],
+ * // '345' => ['id' => '345', 'data' => 'def'],
+ * // ]
+ *
+ * // using anonymous function
+ * $result = ArrayHelper::index($array, function ($element) {
+ * return $element['id'];
+ * });
+ * ~~~
+ *
+ * @param array $array the array that needs to be indexed
+ * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array
+ * @return array the indexed array
+ */
+ public static function index($array, $key)
+ {
+ $result = [];
+ foreach ($array as $element) {
+ $value = static::getValue($element, $key);
+ $result[$value] = $element;
+ }
- /**
- * Decodes HTML entities into the corresponding characters in an array of strings.
- * Both the array keys and values will be decoded.
- * If a value is an array, this method will also decode it recursively.
- * @param array $data data to be decoded
- * @param boolean $valuesOnly whether to decode array values only. If false,
- * both the array keys and array values will be decoded.
- * @return array the decoded data
- * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
- */
- public static function htmlDecode($data, $valuesOnly = true)
- {
- $d = [];
- foreach ($data as $key => $value) {
- if (!$valuesOnly && is_string($key)) {
- $key = htmlspecialchars_decode($key, ENT_QUOTES);
- }
- if (is_string($value)) {
- $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES);
- } elseif (is_array($value)) {
- $d[$key] = static::htmlDecode($value);
- }
- }
- return $d;
- }
+ return $result;
+ }
+
+ /**
+ * Returns the values of a specified column in an array.
+ * The input array should be multidimensional or an array of objects.
+ *
+ * For example,
+ *
+ * ~~~
+ * $array = [
+ * ['id' => '123', 'data' => 'abc'],
+ * ['id' => '345', 'data' => 'def'],
+ * ];
+ * $result = ArrayHelper::getColumn($array, 'id');
+ * // the result is: ['123', '345']
+ *
+ * // using anonymous function
+ * $result = ArrayHelper::getColumn($array, function ($element) {
+ * return $element['id'];
+ * });
+ * ~~~
+ *
+ * @param array $array
+ * @param string|\Closure $name
+ * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array
+ * will be re-indexed with integers.
+ * @return array the list of column values
+ */
+ public static function getColumn($array, $name, $keepKeys = true)
+ {
+ $result = [];
+ if ($keepKeys) {
+ foreach ($array as $k => $element) {
+ $result[$k] = static::getValue($element, $name);
+ }
+ } else {
+ foreach ($array as $element) {
+ $result[] = static::getValue($element, $name);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Builds a map (key-value pairs) from a multidimensional array or an array of objects.
+ * The `$from` and `$to` parameters specify the key names or property names to set up the map.
+ * Optionally, one can further group the map according to a grouping field `$group`.
+ *
+ * For example,
+ *
+ * ~~~
+ * $array = [
+ * ['id' => '123', 'name' => 'aaa', 'class' => 'x'],
+ * ['id' => '124', 'name' => 'bbb', 'class' => 'x'],
+ * ['id' => '345', 'name' => 'ccc', 'class' => 'y'],
+ * );
+ *
+ * $result = ArrayHelper::map($array, 'id', 'name');
+ * // the result is:
+ * // [
+ * // '123' => 'aaa',
+ * // '124' => 'bbb',
+ * // '345' => 'ccc',
+ * // ]
+ *
+ * $result = ArrayHelper::map($array, 'id', 'name', 'class');
+ * // the result is:
+ * // [
+ * // 'x' => [
+ * // '123' => 'aaa',
+ * // '124' => 'bbb',
+ * // ],
+ * // 'y' => [
+ * // '345' => 'ccc',
+ * // ],
+ * // ]
+ * ~~~
+ *
+ * @param array $array
+ * @param string|\Closure $from
+ * @param string|\Closure $to
+ * @param string|\Closure $group
+ * @return array
+ */
+ public static function map($array, $from, $to, $group = null)
+ {
+ $result = [];
+ foreach ($array as $element) {
+ $key = static::getValue($element, $from);
+ $value = static::getValue($element, $to);
+ if ($group !== null) {
+ $result[static::getValue($element, $group)][$key] = $value;
+ } else {
+ $result[$key] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Checks if the given array contains the specified key.
+ * This method enhances the `array_key_exists()` function by supporting case-insensitive
+ * key comparison.
+ * @param string $key the key to check
+ * @param array $array the array with keys to check
+ * @param boolean $caseSensitive whether the key comparison should be case-sensitive
+ * @return boolean whether the array contains the specified key
+ */
+ public static function keyExists($key, $array, $caseSensitive = true)
+ {
+ if ($caseSensitive) {
+ return array_key_exists($key, $array);
+ } else {
+ foreach (array_keys($array) as $k) {
+ if (strcasecmp($key, $k) === 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Sorts an array of objects or arrays (with the same structure) by one or several keys.
+ * @param array $array the array to be sorted. The array will be modified after calling this method.
+ * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
+ * elements, a property name of the objects, or an anonymous function returning the values for comparison
+ * purpose. The anonymous function signature should be: `function($item)`.
+ * To sort by multiple keys, provide an array of keys here.
+ * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
+ * When sorting by multiple keys with different sorting directions, use an array of sorting directions.
+ * @param integer|array $sortFlag the PHP sort flag. Valid values include
+ * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`.
+ * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php)
+ * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags.
+ * @throws InvalidParamException if the $descending or $sortFlag parameters do not have
+ * correct number of elements as that of $key.
+ */
+ public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR)
+ {
+ $keys = is_array($key) ? $key : [$key];
+ if (empty($keys) || empty($array)) {
+ return;
+ }
+ $n = count($keys);
+ if (is_scalar($direction)) {
+ $direction = array_fill(0, $n, $direction);
+ } elseif (count($direction) !== $n) {
+ throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.');
+ }
+ if (is_scalar($sortFlag)) {
+ $sortFlag = array_fill(0, $n, $sortFlag);
+ } elseif (count($sortFlag) !== $n) {
+ throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.');
+ }
+ $args = [];
+ foreach ($keys as $i => $key) {
+ $flag = $sortFlag[$i];
+ $args[] = static::getColumn($array, $key);
+ $args[] = $direction[$i];
+ $args[] = $flag;
+ }
+ $args[] = &$array;
+ call_user_func_array('array_multisort', $args);
+ }
+
+ /**
+ * Encodes special characters in an array of strings into HTML entities.
+ * Both the array keys and values will be encoded.
+ * If a value is an array, this method will also encode it recursively.
+ * @param array $data data to be encoded
+ * @param boolean $valuesOnly whether to encode array values only. If false,
+ * both the array keys and array values will be encoded.
+ * @param string $charset the charset that the data is using. If not set,
+ * [[\yii\base\Application::charset]] will be used.
+ * @return array the encoded data
+ * @see http://www.php.net/manual/en/function.htmlspecialchars.php
+ */
+ public static function htmlEncode($data, $valuesOnly = true, $charset = null)
+ {
+ if ($charset === null) {
+ $charset = Yii::$app->charset;
+ }
+ $d = [];
+ foreach ($data as $key => $value) {
+ if (!$valuesOnly && is_string($key)) {
+ $key = htmlspecialchars($key, ENT_QUOTES, $charset);
+ }
+ if (is_string($value)) {
+ $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset);
+ } elseif (is_array($value)) {
+ $d[$key] = static::htmlEncode($value, $charset);
+ }
+ }
+
+ return $d;
+ }
+
+ /**
+ * Decodes HTML entities into the corresponding characters in an array of strings.
+ * Both the array keys and values will be decoded.
+ * If a value is an array, this method will also decode it recursively.
+ * @param array $data data to be decoded
+ * @param boolean $valuesOnly whether to decode array values only. If false,
+ * both the array keys and array values will be decoded.
+ * @return array the decoded data
+ * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
+ */
+ public static function htmlDecode($data, $valuesOnly = true)
+ {
+ $d = [];
+ foreach ($data as $key => $value) {
+ if (!$valuesOnly && is_string($key)) {
+ $key = htmlspecialchars_decode($key, ENT_QUOTES);
+ }
+ if (is_string($value)) {
+ $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES);
+ } elseif (is_array($value)) {
+ $d[$key] = static::htmlDecode($value);
+ }
+ }
+
+ return $d;
+ }
}
diff --git a/framework/helpers/BaseConsole.php b/framework/helpers/BaseConsole.php
index 458f212958b..ad2b7a0f17c 100644
--- a/framework/helpers/BaseConsole.php
+++ b/framework/helpers/BaseConsole.php
@@ -17,906 +17,914 @@
*/
class BaseConsole
{
- const FG_BLACK = 30;
- const FG_RED = 31;
- const FG_GREEN = 32;
- const FG_YELLOW = 33;
- const FG_BLUE = 34;
- const FG_PURPLE = 35;
- const FG_CYAN = 36;
- const FG_GREY = 37;
-
- const BG_BLACK = 40;
- const BG_RED = 41;
- const BG_GREEN = 42;
- const BG_YELLOW = 43;
- const BG_BLUE = 44;
- const BG_PURPLE = 45;
- const BG_CYAN = 46;
- const BG_GREY = 47;
-
- const RESET = 0;
- const NORMAL = 0;
- const BOLD = 1;
- const ITALIC = 3;
- const UNDERLINE = 4;
- const BLINK = 5;
- const NEGATIVE = 7;
- const CONCEALED = 8;
- const CROSSED_OUT = 9;
- const FRAMED = 51;
- const ENCIRCLED = 52;
- const OVERLINED = 53;
-
- /**
- * Moves the terminal cursor up by sending ANSI control code CUU to the terminal.
- * If the cursor is already at the edge of the screen, this has no effect.
- * @param integer $rows number of rows the cursor should be moved up
- */
- public static function moveCursorUp($rows = 1)
- {
- echo "\033[" . (int)$rows . 'A';
- }
-
- /**
- * Moves the terminal cursor down by sending ANSI control code CUD to the terminal.
- * If the cursor is already at the edge of the screen, this has no effect.
- * @param integer $rows number of rows the cursor should be moved down
- */
- public static function moveCursorDown($rows = 1)
- {
- echo "\033[" . (int)$rows . 'B';
- }
-
- /**
- * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal.
- * If the cursor is already at the edge of the screen, this has no effect.
- * @param integer $steps number of steps the cursor should be moved forward
- */
- public static function moveCursorForward($steps = 1)
- {
- echo "\033[" . (int)$steps . 'C';
- }
-
- /**
- * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal.
- * If the cursor is already at the edge of the screen, this has no effect.
- * @param integer $steps number of steps the cursor should be moved backward
- */
- public static function moveCursorBackward($steps = 1)
- {
- echo "\033[" . (int)$steps . 'D';
- }
-
- /**
- * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal.
- * @param integer $lines number of lines the cursor should be moved down
- */
- public static function moveCursorNextLine($lines = 1)
- {
- echo "\033[" . (int)$lines . 'E';
- }
-
- /**
- * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal.
- * @param integer $lines number of lines the cursor should be moved up
- */
- public static function moveCursorPrevLine($lines = 1)
- {
- echo "\033[" . (int)$lines . 'F';
- }
-
- /**
- * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal.
- * @param integer $column 1-based column number, 1 is the left edge of the screen.
- * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line.
- */
- public static function moveCursorTo($column, $row = null)
- {
- if ($row === null) {
- echo "\033[" . (int)$column . 'G';
- } else {
- echo "\033[" . (int)$row . ';' . (int)$column . 'H';
- }
- }
-
- /**
- * Scrolls whole page up by sending ANSI control code SU to the terminal.
- * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows.
- * @param integer $lines number of lines to scroll up
- */
- public static function scrollUp($lines = 1)
- {
- echo "\033[" . (int)$lines . "S";
- }
-
- /**
- * Scrolls whole page down by sending ANSI control code SD to the terminal.
- * New lines are added at the top. This is not supported by ANSI.SYS used in windows.
- * @param integer $lines number of lines to scroll down
- */
- public static function scrollDown($lines = 1)
- {
- echo "\033[" . (int)$lines . "T";
- }
-
- /**
- * Saves the current cursor position by sending ANSI control code SCP to the terminal.
- * Position can then be restored with [[restoreCursorPosition()]].
- */
- public static function saveCursorPosition()
- {
- echo "\033[s";
- }
-
- /**
- * Restores the cursor position saved with [[saveCursorPosition()]] by sending ANSI control code RCP to the terminal.
- */
- public static function restoreCursorPosition()
- {
- echo "\033[u";
- }
-
- /**
- * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal.
- * Use [[showCursor()]] to bring it back.
- * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit.
- */
- public static function hideCursor()
- {
- echo "\033[?25l";
- }
-
- /**
- * Will show a cursor again when it has been hidden by [[hideCursor()]] by sending ANSI DECTCEM code ?25h to the terminal.
- */
- public static function showCursor()
- {
- echo "\033[?25h";
- }
-
- /**
- * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal.
- * Cursor position will not be changed.
- * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen.
- */
- public static function clearScreen()
- {
- echo "\033[2J";
- }
-
- /**
- * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal.
- * Cursor position will not be changed.
- */
- public static function clearScreenBeforeCursor()
- {
- echo "\033[1J";
- }
-
- /**
- * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal.
- * Cursor position will not be changed.
- */
- public static function clearScreenAfterCursor()
- {
- echo "\033[0J";
- }
-
- /**
- * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal.
- * Cursor position will not be changed.
- */
- public static function clearLine()
- {
- echo "\033[2K";
- }
-
- /**
- * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal.
- * Cursor position will not be changed.
- */
- public static function clearLineBeforeCursor()
- {
- echo "\033[1K";
- }
-
- /**
- * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal.
- * Cursor position will not be changed.
- */
- public static function clearLineAfterCursor()
- {
- echo "\033[0K";
- }
-
- /**
- * Returns the ANSI format code.
- *
- * @param array $format An array containing formatting values.
- * You can pass any of the FG_*, BG_* and TEXT_* constants
- * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
- * @return string The ANSI format code according to the given formatting constants.
- */
- public static function ansiFormatCode($format)
- {
- return "\033[" . implode(';', $format) . 'm';
- }
-
- /**
- * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards.
- *
- * @param array $format An array containing formatting values.
- * You can pass any of the FG_*, BG_* and TEXT_* constants
- * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
- * @see ansiFormatCode()
- * @see endAnsiFormat()
- */
- public static function beginAnsiFormat($format)
- {
- echo "\033[" . implode(';', $format) . 'm';
- }
-
- /**
- * Resets any ANSI format set by previous method [[beginAnsiFormat()]]
- * Any output after this will have default text format.
- * This is equal to calling
- *
- * ```php
- * echo Console::ansiFormatCode([Console::RESET])
- * ```
- */
- public static function endAnsiFormat()
- {
- echo "\033[0m";
- }
-
- /**
- * Will return a string formatted with the given ANSI style
- *
- * @param string $string the string to be formatted
- * @param array $format An array containing formatting values.
- * You can pass any of the FG_*, BG_* and TEXT_* constants
- * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
- * @return string
- */
- public static function ansiFormat($string, $format = [])
- {
- $code = implode(';', $format);
- return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m";
- }
-
- /**
- * Returns the ansi format code for xterm foreground color.
- * You can pass the return value of this to one of the formatting methods:
- * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]]
- *
- * @param integer $colorCode xterm color code
- * @return string
- * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
- */
- public static function xtermFgColor($colorCode)
- {
- return '38;5;' . $colorCode;
- }
-
- /**
- * Returns the ansi format code for xterm background color.
- * You can pass the return value of this to one of the formatting methods:
- * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]]
- *
- * @param integer $colorCode xterm color code
- * @return string
- * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
- */
- public static function xtermBgColor($colorCode)
- {
- return '48;5;' . $colorCode;
- }
-
- /**
- * Strips ANSI control codes from a string
- *
- * @param string $string String to strip
- * @return string
- */
- public static function stripAnsiFormat($string)
- {
- return preg_replace('/\033\[[\d;?]*\w/', '', $string);
- }
-
- /**
- * Converts an ANSI formatted string to HTML
- * @param $string
- * @return mixed
- */
- // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
- public static function ansiToHtml($string)
- {
- $tags = 0;
- return preg_replace_callback(
- '/\033\[[\d;]+m/',
- function ($ansi) use (&$tags) {
- $styleA = [];
- foreach (explode(';', $ansi) as $controlCode) {
- switch ($controlCode) {
- case self::FG_BLACK:
- $style = ['color' => '#000000'];
- break;
- case self::FG_BLUE:
- $style = ['color' => '#000078'];
- break;
- case self::FG_CYAN:
- $style = ['color' => '#007878'];
- break;
- case self::FG_GREEN:
- $style = ['color' => '#007800'];
- break;
- case self::FG_GREY:
- $style = ['color' => '#787878'];
- break;
- case self::FG_PURPLE:
- $style = ['color' => '#780078'];
- break;
- case self::FG_RED:
- $style = ['color' => '#780000'];
- break;
- case self::FG_YELLOW:
- $style = ['color' => '#787800'];
- break;
- case self::BG_BLACK:
- $style = ['background-color' => '#000000'];
- break;
- case self::BG_BLUE:
- $style = ['background-color' => '#000078'];
- break;
- case self::BG_CYAN:
- $style = ['background-color' => '#007878'];
- break;
- case self::BG_GREEN:
- $style = ['background-color' => '#007800'];
- break;
- case self::BG_GREY:
- $style = ['background-color' => '#787878'];
- break;
- case self::BG_PURPLE:
- $style = ['background-color' => '#780078'];
- break;
- case self::BG_RED:
- $style = ['background-color' => '#780000'];
- break;
- case self::BG_YELLOW:
- $style = ['background-color' => '#787800'];
- break;
- case self::BOLD:
- $style = ['font-weight' => 'bold'];
- break;
- case self::ITALIC:
- $style = ['font-style' => 'italic'];
- break;
- case self::UNDERLINE:
- $style = ['text-decoration' => ['underline']];
- break;
- case self::OVERLINED:
- $style = ['text-decoration' => ['overline']];
- break;
- case self::CROSSED_OUT:
- $style = ['text-decoration' => ['line-through']];
- break;
- case self::BLINK:
- $style = ['text-decoration' => ['blink']];
- break;
- case self::NEGATIVE: // ???
- case self::CONCEALED:
- case self::ENCIRCLED:
- case self::FRAMED:
- // TODO allow resetting codes
- break;
- case 0: // ansi reset
- $return = '';
- for (; $tags > 0; $tags--) {
- $return .= '';
- }
- return $return;
- }
-
- $styleA = ArrayHelper::merge($styleA, $style);
- }
- $styleString = [];
- foreach ($styleA as $name => $content) {
- if ($name === 'text-decoration') {
- $content = implode(' ', $content);
- }
- $styleString[] = $name . ':' . $content;
- }
- $tags++;
- return '';
- },
- $string
- );
- }
-
- // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
- public function markdownToAnsi()
- {
- // TODO implement
- }
-
- /**
- * Converts a string to ansi formatted by replacing patterns like %y (for yellow) with ansi control codes
- *
- * Uses almost the same syntax as https://github.com/pear/Console_Color2/blob/master/Console/Color2.php
- * The conversion table is: ('bold' meaning 'light' on some
- * terminals). It's almost the same conversion table irssi uses.
- *
- * text text background
- * ------------------------------------------------
- * %k %K %0 black dark grey black
- * %r %R %1 red bold red red
- * %g %G %2 green bold green green
- * %y %Y %3 yellow bold yellow yellow
- * %b %B %4 blue bold blue blue
- * %m %M %5 magenta bold magenta magenta
- * %p %P magenta (think: purple)
- * %c %C %6 cyan bold cyan cyan
- * %w %W %7 white bold white white
- *
- * %F Blinking, Flashing
- * %U Underline
- * %8 Reverse
- * %_,%9 Bold
- *
- * %n Resets the color
- * %% A single %
- *
- * First param is the string to convert, second is an optional flag if
- * colors should be used. It defaults to true, if set to false, the
- * colorcodes will just be removed (And %% will be transformed into %)
- *
- * @param string $string String to convert
- * @param boolean $colored Should the string be colored?
- * @return string
- */
- // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
- public static function renderColoredString($string, $colored = true)
- {
- static $conversions = [
- '%y' => [self::FG_YELLOW],
- '%g' => [self::FG_GREEN],
- '%b' => [self::FG_BLUE],
- '%r' => [self::FG_RED],
- '%p' => [self::FG_PURPLE],
- '%m' => [self::FG_PURPLE],
- '%c' => [self::FG_CYAN],
- '%w' => [self::FG_GREY],
- '%k' => [self::FG_BLACK],
- '%n' => [0], // reset
- '%Y' => [self::FG_YELLOW, self::BOLD],
- '%G' => [self::FG_GREEN, self::BOLD],
- '%B' => [self::FG_BLUE, self::BOLD],
- '%R' => [self::FG_RED, self::BOLD],
- '%P' => [self::FG_PURPLE, self::BOLD],
- '%M' => [self::FG_PURPLE, self::BOLD],
- '%C' => [self::FG_CYAN, self::BOLD],
- '%W' => [self::FG_GREY, self::BOLD],
- '%K' => [self::FG_BLACK, self::BOLD],
- '%N' => [0, self::BOLD],
- '%3' => [self::BG_YELLOW],
- '%2' => [self::BG_GREEN],
- '%4' => [self::BG_BLUE],
- '%1' => [self::BG_RED],
- '%5' => [self::BG_PURPLE],
- '%6' => [self::BG_PURPLE],
- '%7' => [self::BG_CYAN],
- '%0' => [self::BG_GREY],
- '%F' => [self::BLINK],
- '%U' => [self::UNDERLINE],
- '%8' => [self::NEGATIVE],
- '%9' => [self::BOLD],
- '%_' => [self::BOLD],
- ];
-
- if ($colored) {
- $string = str_replace('%%', '% ', $string);
- foreach ($conversions as $key => $value) {
- $string = str_replace(
- $key,
- static::ansiFormatCode($value),
- $string
- );
- }
- $string = str_replace('% ', '%', $string);
- } else {
- $string = preg_replace('/%((%)|.)/', '$2', $string);
- }
- return $string;
- }
-
- /**
- * Escapes % so they don't get interpreted as color codes when
- * the string is parsed by [[renderColoredString]]
- *
- * @param string $string String to escape
- *
- * @access public
- * @return string
- */
- // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
- public static function escape($string)
- {
- return str_replace('%', '%%', $string);
- }
-
- /**
- * Returns true if the stream supports colorization. ANSI colors are disabled if not supported by the stream.
- *
- * - windows without ansicon
- * - not tty consoles
- *
- * @param mixed $stream
- * @return boolean true if the stream supports ANSI colors, otherwise false.
- */
- public static function streamSupportsAnsiColors($stream)
- {
- return DIRECTORY_SEPARATOR == '\\'
- ? getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON'
- : function_exists('posix_isatty') && @posix_isatty($stream);
- }
-
- /**
- * Returns true if the console is running on windows
- * @return bool
- */
- public static function isRunningOnWindows()
- {
- return DIRECTORY_SEPARATOR == '\\';
- }
-
- /**
- * Usage: list($width, $height) = ConsoleHelper::getScreenSize();
- *
- * @param boolean $refresh whether to force checking and not re-use cached size value.
- * This is useful to detect changing window size while the application is running but may
- * not get up to date values on every terminal.
- * @return array|boolean An array of ($width, $height) or false when it was not able to determine size.
- */
- public static function getScreenSize($refresh = false)
- {
- static $size;
- if ($size !== null && !$refresh) {
- return $size;
- }
-
- if (static::isRunningOnWindows()) {
- $output = [];
- exec('mode con', $output);
- if (isset($output) && strpos($output[1], 'CON') !== false) {
- return $size = [(int)preg_replace('~[^0-9]~', '', $output[3]), (int)preg_replace('~[^0-9]~', '', $output[4])];
- }
- } else {
- // try stty if available
- $stty = [];
- if (exec('stty -a 2>&1', $stty) && preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches)) {
- return $size = [$matches[2], $matches[1]];
- }
-
- // fallback to tput, which may not be updated on terminal resize
- if (($width = (int) exec('tput cols 2>&1')) > 0 && ($height = (int) exec('tput lines 2>&1')) > 0) {
- return $size = [$width, $height];
- }
-
- // fallback to ENV variables, which may not be updated on terminal resize
- if (($width = (int) getenv('COLUMNS')) > 0 && ($height = (int) getenv('LINES')) > 0) {
- return $size = [$width, $height];
- }
- }
-
- return $size = false;
- }
-
- /**
- * Gets input from STDIN and returns a string right-trimmed for EOLs.
- *
- * @param boolean $raw If set to true, returns the raw string without trimming
- * @return string the string read from stdin
- */
- public static function stdin($raw = false)
- {
- return $raw ? fgets(STDIN) : rtrim(fgets(STDIN), PHP_EOL);
- }
-
- /**
- * Prints a string to STDOUT.
- *
- * @param string $string the string to print
- * @return int|boolean Number of bytes printed or false on error
- */
- public static function stdout($string)
- {
- return fwrite(STDOUT, $string);
- }
-
- /**
- * Prints a string to STDERR.
- *
- * @param string $string the string to print
- * @return int|boolean Number of bytes printed or false on error
- */
- public static function stderr($string)
- {
- return fwrite(STDERR, $string);
- }
-
- /**
- * Asks the user for input. Ends when the user types a carriage return (PHP_EOL). Optionally, It also provides a
- * prompt.
- *
- * @param string $prompt the prompt to display before waiting for input (optional)
- * @return string the user's input
- */
- public static function input($prompt = null)
- {
- if (isset($prompt)) {
- static::stdout($prompt);
- }
- return static::stdin();
- }
-
- /**
- * Prints text to STDOUT appended with a carriage return (PHP_EOL).
- *
- * @param string $string the text to print
- * @return integer|boolean number of bytes printed or false on error.
- */
- public static function output($string = null)
- {
- return static::stdout($string . PHP_EOL);
- }
-
- /**
- * Prints text to STDERR appended with a carriage return (PHP_EOL).
- *
- * @param string $string the text to print
- * @return integer|boolean number of bytes printed or false on error.
- */
- public static function error($string = null)
- {
- return static::stderr($string . PHP_EOL);
- }
-
- /**
- * Prompts the user for input and validates it
- *
- * @param string $text prompt string
- * @param array $options the options to validate the input:
- * - required: whether it is required or not
- * - default: default value if no input is inserted by the user
- * - pattern: regular expression pattern to validate user input
- * - validator: a callable function to validate input. The function must accept two parameters:
- * - $input: the user input to validate
- * - $error: the error value passed by reference if validation failed.
- * @return string the user input
- */
- public static function prompt($text, $options = [])
- {
- $options = ArrayHelper::merge(
- [
- 'required' => false,
- 'default' => null,
- 'pattern' => null,
- 'validator' => null,
- 'error' => 'Invalid input.',
- ],
- $options
- );
- $error = null;
-
- top:
- $input = $options['default']
- ? static::input("$text [" . $options['default'] . ']: ')
- : static::input("$text: ");
-
- if (!strlen($input)) {
- if (isset($options['default'])) {
- $input = $options['default'];
- } elseif ($options['required']) {
- static::output($options['error']);
- goto top;
- }
- } elseif ($options['pattern'] && !preg_match($options['pattern'], $input)) {
- static::output($options['error']);
- goto top;
- } elseif ($options['validator'] &&
- !call_user_func_array($options['validator'], [$input, &$error])
- ) {
- static::output(isset($error) ? $error : $options['error']);
- goto top;
- }
-
- return $input;
- }
-
- /**
- * Asks user to confirm by typing y or n.
- *
- * @param string $message to echo out before waiting for user input
- * @param boolean $default this value is returned if no selection is made.
- * @return boolean whether user confirmed
- */
- public static function confirm($message, $default = true)
- {
- echo $message . ' (yes|no) [' . ($default ? 'yes' : 'no') . ']:';
- $input = trim(static::stdin());
- return empty($input) ? $default : !strncasecmp($input, 'y', 1);
- }
-
- /**
- * Gives the user an option to choose from. Giving '?' as an input will show
- * a list of options to choose from and their explanations.
- *
- * @param string $prompt the prompt message
- * @param array $options Key-value array of options to choose from
- *
- * @return string An option character the user chose
- */
- public static function select($prompt, $options = [])
- {
- top:
- static::stdout("$prompt [" . implode(',', array_keys($options)) . ",?]: ");
- $input = static::stdin();
- if ($input === '?') {
- foreach ($options as $key => $value) {
- static::output(" $key - $value");
- }
- static::output(" ? - Show help");
- goto top;
- } elseif (!in_array($input, array_keys($options))) {
- goto top;
- }
- return $input;
- }
-
- private static $_progressStart;
- private static $_progressWidth;
- private static $_progressPrefix;
-
- /**
- * Starts display of a progress bar on screen.
- *
- * This bar will be updated by [[updateProgress()]] and my be ended by [[endProgress()]].
- *
- * The following example shows a simple usage of a progress bar:
- *
- * ```php
- * Console::startProgress(0, 1000);
- * for ($n = 1; $n <= 1000; $n++) {
- * usleep(1000);
- * Console::updateProgress($n, 1000);
- * }
- * Console::endProgress();
- * ```
- *
- * Git clone like progress (showing only status information):
- * ```php
- * Console::startProgress(0, 1000, 'Counting objects: ', false);
- * for ($n = 1; $n <= 1000; $n++) {
- * usleep(1000);
- * Console::updateProgress($n, 1000);
- * }
- * Console::endProgress("done." . PHP_EOL);
- * ```
- *
- * @param integer $done the number of items that are completed.
- * @param integer $total the total value of items that are to be done.
- * @param string $prefix an optional string to display before the progress bar.
- * Default to empty string which results in no prefix to be displayed.
- * @param integer|boolean $width optional width of the progressbar. This can be an integer representing
- * the number of characters to display for the progress bar or a float between 0 and 1 representing the
- * percentage of screen with the progress bar may take. It can also be set to false to disable the
- * bar and only show progress information like percent, number of items and ETA.
- * If not set, the bar will be as wide as the screen. Screen size will be detected using [[getScreenSize()]].
- * @see startProgress
- * @see updateProgress
- * @see endProgress
- */
- public static function startProgress($done, $total, $prefix = '', $width = null)
- {
- self::$_progressStart = time();
- self::$_progressWidth = $width;
- self::$_progressPrefix = $prefix;
-
- static::updateProgress($done, $total);
- }
-
- /**
- * Updates a progress bar that has been started by [[startProgress()]].
- *
- * @param integer $done the number of items that are completed.
- * @param integer $total the total value of items that are to be done.
- * @param string $prefix an optional string to display before the progress bar.
- * Defaults to null meaning the prefix specified by [[startProgress()]] will be used.
- * If prefix is specified it will update the prefix that will be used by later calls.
- * @see startProgress
- * @see endProgress
- */
- public static function updateProgress($done, $total, $prefix = null)
- {
- $width = self::$_progressWidth;
- if ($width === false) {
- $width = 0;
- } else {
- $screenSize = static::getScreenSize(true);
- if ($screenSize === false && $width < 1) {
- $width = 0;
- } elseif ($width === null) {
- $width = $screenSize[0];
- } elseif ($width > 0 && $width < 1) {
- $width = floor($screenSize[0] * $width);
- }
- }
- if ($prefix === null) {
- $prefix = self::$_progressPrefix;
- } else {
- self::$_progressPrefix = $prefix;
- }
- $width -= mb_strlen($prefix);
-
- $percent = ($total == 0) ? 1 : $done / $total;
- $info = sprintf("%d%% (%d/%d)", $percent * 100, $done, $total);
-
- if ($done > $total || $done == 0) {
- $info .= ' ETA: n/a';
- } elseif ($done < $total) {
- $rate = (time() - self::$_progressStart) / $done;
- $info .= sprintf(' ETA: %d sec.', $rate * ($total - $done));
- }
-
- $width -= 3 + mb_strlen($info);
- // skipping progress bar on very small display or if forced to skip
- if ($width < 5) {
- static::stdout("\r$prefix$info ");
- } else {
- if ($percent < 0) {
- $percent = 0;
- } elseif ($percent > 1) {
- $percent = 1;
- }
- $bar = floor($percent * $width);
- $status = str_repeat("=", $bar);
- if ($bar < $width) {
- $status .= ">";
- $status .= str_repeat(" ", $width - $bar - 1);
- }
- static::stdout("\r$prefix" . "[$status] $info");
- }
- flush();
- }
-
- /**
- * Ends a progress bar that has been started by [[startProgress()]].
- *
- * @param string|boolean $remove This can be `false` to leave the progress bar on screen and just print a newline.
- * If set to `true`, the line of the progress bar will be cleared. This may also be a string to be displayed instead
- * of the progress bar.
- * @param boolean $keepPrefix whether to keep the prefix that has been specified for the progressbar when progressbar
- * gets removed. Defaults to true.
- * @see startProgress
- * @see updateProgress
- */
- public static function endProgress($remove = false, $keepPrefix = true)
- {
- if ($remove === false) {
- static::stdout(PHP_EOL);
- } else {
- if (static::streamSupportsAnsiColors(STDOUT)) {
- static::clearLine();
- }
- static::stdout("\r" . ($keepPrefix ? self::$_progressPrefix : '') . (is_string($remove) ? $remove : ''));
- }
- flush();
-
- self::$_progressStart = null;
- self::$_progressWidth = null;
- self::$_progressPrefix = '';
- }
+ const FG_BLACK = 30;
+ const FG_RED = 31;
+ const FG_GREEN = 32;
+ const FG_YELLOW = 33;
+ const FG_BLUE = 34;
+ const FG_PURPLE = 35;
+ const FG_CYAN = 36;
+ const FG_GREY = 37;
+
+ const BG_BLACK = 40;
+ const BG_RED = 41;
+ const BG_GREEN = 42;
+ const BG_YELLOW = 43;
+ const BG_BLUE = 44;
+ const BG_PURPLE = 45;
+ const BG_CYAN = 46;
+ const BG_GREY = 47;
+
+ const RESET = 0;
+ const NORMAL = 0;
+ const BOLD = 1;
+ const ITALIC = 3;
+ const UNDERLINE = 4;
+ const BLINK = 5;
+ const NEGATIVE = 7;
+ const CONCEALED = 8;
+ const CROSSED_OUT = 9;
+ const FRAMED = 51;
+ const ENCIRCLED = 52;
+ const OVERLINED = 53;
+
+ /**
+ * Moves the terminal cursor up by sending ANSI control code CUU to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $rows number of rows the cursor should be moved up
+ */
+ public static function moveCursorUp($rows = 1)
+ {
+ echo "\033[" . (int) $rows . 'A';
+ }
+
+ /**
+ * Moves the terminal cursor down by sending ANSI control code CUD to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $rows number of rows the cursor should be moved down
+ */
+ public static function moveCursorDown($rows = 1)
+ {
+ echo "\033[" . (int) $rows . 'B';
+ }
+
+ /**
+ * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $steps number of steps the cursor should be moved forward
+ */
+ public static function moveCursorForward($steps = 1)
+ {
+ echo "\033[" . (int) $steps . 'C';
+ }
+
+ /**
+ * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $steps number of steps the cursor should be moved backward
+ */
+ public static function moveCursorBackward($steps = 1)
+ {
+ echo "\033[" . (int) $steps . 'D';
+ }
+
+ /**
+ * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal.
+ * @param integer $lines number of lines the cursor should be moved down
+ */
+ public static function moveCursorNextLine($lines = 1)
+ {
+ echo "\033[" . (int) $lines . 'E';
+ }
+
+ /**
+ * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal.
+ * @param integer $lines number of lines the cursor should be moved up
+ */
+ public static function moveCursorPrevLine($lines = 1)
+ {
+ echo "\033[" . (int) $lines . 'F';
+ }
+
+ /**
+ * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal.
+ * @param integer $column 1-based column number, 1 is the left edge of the screen.
+ * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line.
+ */
+ public static function moveCursorTo($column, $row = null)
+ {
+ if ($row === null) {
+ echo "\033[" . (int) $column . 'G';
+ } else {
+ echo "\033[" . (int) $row . ';' . (int) $column . 'H';
+ }
+ }
+
+ /**
+ * Scrolls whole page up by sending ANSI control code SU to the terminal.
+ * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows.
+ * @param integer $lines number of lines to scroll up
+ */
+ public static function scrollUp($lines = 1)
+ {
+ echo "\033[" . (int) $lines . "S";
+ }
+
+ /**
+ * Scrolls whole page down by sending ANSI control code SD to the terminal.
+ * New lines are added at the top. This is not supported by ANSI.SYS used in windows.
+ * @param integer $lines number of lines to scroll down
+ */
+ public static function scrollDown($lines = 1)
+ {
+ echo "\033[" . (int) $lines . "T";
+ }
+
+ /**
+ * Saves the current cursor position by sending ANSI control code SCP to the terminal.
+ * Position can then be restored with [[restoreCursorPosition()]].
+ */
+ public static function saveCursorPosition()
+ {
+ echo "\033[s";
+ }
+
+ /**
+ * Restores the cursor position saved with [[saveCursorPosition()]] by sending ANSI control code RCP to the terminal.
+ */
+ public static function restoreCursorPosition()
+ {
+ echo "\033[u";
+ }
+
+ /**
+ * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal.
+ * Use [[showCursor()]] to bring it back.
+ * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit.
+ */
+ public static function hideCursor()
+ {
+ echo "\033[?25l";
+ }
+
+ /**
+ * Will show a cursor again when it has been hidden by [[hideCursor()]] by sending ANSI DECTCEM code ?25h to the terminal.
+ */
+ public static function showCursor()
+ {
+ echo "\033[?25h";
+ }
+
+ /**
+ * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal.
+ * Cursor position will not be changed.
+ * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen.
+ */
+ public static function clearScreen()
+ {
+ echo "\033[2J";
+ }
+
+ /**
+ * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearScreenBeforeCursor()
+ {
+ echo "\033[1J";
+ }
+
+ /**
+ * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearScreenAfterCursor()
+ {
+ echo "\033[0J";
+ }
+
+ /**
+ * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearLine()
+ {
+ echo "\033[2K";
+ }
+
+ /**
+ * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearLineBeforeCursor()
+ {
+ echo "\033[1K";
+ }
+
+ /**
+ * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearLineAfterCursor()
+ {
+ echo "\033[0K";
+ }
+
+ /**
+ * Returns the ANSI format code.
+ *
+ * @param array $format An array containing formatting values.
+ * You can pass any of the FG_*, BG_* and TEXT_* constants
+ * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
+ * @return string The ANSI format code according to the given formatting constants.
+ */
+ public static function ansiFormatCode($format)
+ {
+ return "\033[" . implode(';', $format) . 'm';
+ }
+
+ /**
+ * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards.
+ *
+ * @param array $format An array containing formatting values.
+ * You can pass any of the FG_*, BG_* and TEXT_* constants
+ * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
+ * @see ansiFormatCode()
+ * @see endAnsiFormat()
+ */
+ public static function beginAnsiFormat($format)
+ {
+ echo "\033[" . implode(';', $format) . 'm';
+ }
+
+ /**
+ * Resets any ANSI format set by previous method [[beginAnsiFormat()]]
+ * Any output after this will have default text format.
+ * This is equal to calling
+ *
+ * ```php
+ * echo Console::ansiFormatCode([Console::RESET])
+ * ```
+ */
+ public static function endAnsiFormat()
+ {
+ echo "\033[0m";
+ }
+
+ /**
+ * Will return a string formatted with the given ANSI style
+ *
+ * @param string $string the string to be formatted
+ * @param array $format An array containing formatting values.
+ * You can pass any of the FG_*, BG_* and TEXT_* constants
+ * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
+ * @return string
+ */
+ public static function ansiFormat($string, $format = [])
+ {
+ $code = implode(';', $format);
+
+ return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m";
+ }
+
+ /**
+ * Returns the ansi format code for xterm foreground color.
+ * You can pass the return value of this to one of the formatting methods:
+ * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]]
+ *
+ * @param integer $colorCode xterm color code
+ * @return string
+ * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
+ */
+ public static function xtermFgColor($colorCode)
+ {
+ return '38;5;' . $colorCode;
+ }
+
+ /**
+ * Returns the ansi format code for xterm background color.
+ * You can pass the return value of this to one of the formatting methods:
+ * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]]
+ *
+ * @param integer $colorCode xterm color code
+ * @return string
+ * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
+ */
+ public static function xtermBgColor($colorCode)
+ {
+ return '48;5;' . $colorCode;
+ }
+
+ /**
+ * Strips ANSI control codes from a string
+ *
+ * @param string $string String to strip
+ * @return string
+ */
+ public static function stripAnsiFormat($string)
+ {
+ return preg_replace('/\033\[[\d;?]*\w/', '', $string);
+ }
+
+ /**
+ * Converts an ANSI formatted string to HTML
+ * @param $string
+ * @return mixed
+ */
+ // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
+ public static function ansiToHtml($string)
+ {
+ $tags = 0;
+
+ return preg_replace_callback(
+ '/\033\[[\d;]+m/',
+ function ($ansi) use (&$tags) {
+ $styleA = [];
+ foreach (explode(';', $ansi) as $controlCode) {
+ switch ($controlCode) {
+ case self::FG_BLACK:
+ $style = ['color' => '#000000'];
+ break;
+ case self::FG_BLUE:
+ $style = ['color' => '#000078'];
+ break;
+ case self::FG_CYAN:
+ $style = ['color' => '#007878'];
+ break;
+ case self::FG_GREEN:
+ $style = ['color' => '#007800'];
+ break;
+ case self::FG_GREY:
+ $style = ['color' => '#787878'];
+ break;
+ case self::FG_PURPLE:
+ $style = ['color' => '#780078'];
+ break;
+ case self::FG_RED:
+ $style = ['color' => '#780000'];
+ break;
+ case self::FG_YELLOW:
+ $style = ['color' => '#787800'];
+ break;
+ case self::BG_BLACK:
+ $style = ['background-color' => '#000000'];
+ break;
+ case self::BG_BLUE:
+ $style = ['background-color' => '#000078'];
+ break;
+ case self::BG_CYAN:
+ $style = ['background-color' => '#007878'];
+ break;
+ case self::BG_GREEN:
+ $style = ['background-color' => '#007800'];
+ break;
+ case self::BG_GREY:
+ $style = ['background-color' => '#787878'];
+ break;
+ case self::BG_PURPLE:
+ $style = ['background-color' => '#780078'];
+ break;
+ case self::BG_RED:
+ $style = ['background-color' => '#780000'];
+ break;
+ case self::BG_YELLOW:
+ $style = ['background-color' => '#787800'];
+ break;
+ case self::BOLD:
+ $style = ['font-weight' => 'bold'];
+ break;
+ case self::ITALIC:
+ $style = ['font-style' => 'italic'];
+ break;
+ case self::UNDERLINE:
+ $style = ['text-decoration' => ['underline']];
+ break;
+ case self::OVERLINED:
+ $style = ['text-decoration' => ['overline']];
+ break;
+ case self::CROSSED_OUT:
+ $style = ['text-decoration' => ['line-through']];
+ break;
+ case self::BLINK:
+ $style = ['text-decoration' => ['blink']];
+ break;
+ case self::NEGATIVE: // ???
+ case self::CONCEALED:
+ case self::ENCIRCLED:
+ case self::FRAMED:
+ // TODO allow resetting codes
+ break;
+ case 0: // ansi reset
+ $return = '';
+ for (; $tags > 0; $tags--) {
+ $return .= '';
+ }
+
+ return $return;
+ }
+
+ $styleA = ArrayHelper::merge($styleA, $style);
+ }
+ $styleString = [];
+ foreach ($styleA as $name => $content) {
+ if ($name === 'text-decoration') {
+ $content = implode(' ', $content);
+ }
+ $styleString[] = $name . ':' . $content;
+ }
+ $tags++;
+
+ return '';
+ },
+ $string
+ );
+ }
+
+ // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
+ public function markdownToAnsi()
+ {
+ // TODO implement
+ }
+
+ /**
+ * Converts a string to ansi formatted by replacing patterns like %y (for yellow) with ansi control codes
+ *
+ * Uses almost the same syntax as https://github.com/pear/Console_Color2/blob/master/Console/Color2.php
+ * The conversion table is: ('bold' meaning 'light' on some
+ * terminals). It's almost the same conversion table irssi uses.
+ *
+ * text text background
+ * ------------------------------------------------
+ * %k %K %0 black dark grey black
+ * %r %R %1 red bold red red
+ * %g %G %2 green bold green green
+ * %y %Y %3 yellow bold yellow yellow
+ * %b %B %4 blue bold blue blue
+ * %m %M %5 magenta bold magenta magenta
+ * %p %P magenta (think: purple)
+ * %c %C %6 cyan bold cyan cyan
+ * %w %W %7 white bold white white
+ *
+ * %F Blinking, Flashing
+ * %U Underline
+ * %8 Reverse
+ * %_,%9 Bold
+ *
+ * %n Resets the color
+ * %% A single %
+ *
- This script checks if your server configuration meets the requirements
- for running Yii application.
- It checks if the server is running the right version of PHP,
- if appropriate PHP extensions have been loaded, and if php.ini file settings are correct.
-
-
- There are two kinds of requirements being checked. Mandatory requirements are those that have to be met
- to allow Yii to work as expected. There are also some optional requirements being checked which will
- show you a warning when they do not meet. You can use Yii framework without them but some specific
- functionality may be not available in this case.
-
+
+
Description
+
+ This script checks if your server configuration meets the requirements
+ for running Yii application.
+ It checks if the server is running the right version of PHP,
+ if appropriate PHP extensions have been loaded, and if php.ini file settings are correct.
+
+
+ There are two kinds of requirements being checked. Mandatory requirements are those that have to be met
+ to allow Yii to work as expected. There are also some optional requirements being checked which will
+ show you a warning when they do not meet. You can use Yii framework without them but some specific
+ functionality may be not available in this case.
+
-
Conclusion
- 0): ?>
-
- Unfortunately your server configuration does not satisfy the requirements by this application. Please refer to the table below for detailed explanation.
-
- 0): ?>
-
- Your server configuration satisfies the minimum requirements by this application. Please pay attention to the warnings listed below and check if your application will use the corresponding features.
-
-
-
- Congratulations! Your server configuration satisfies all requirements.
-
-
+
Conclusion
+ 0): ?>
+
+ Unfortunately your server configuration does not satisfy the requirements by this application. Please refer to the table below for detailed explanation.
+
+ 0): ?>
+
+ Your server configuration satisfies the minimum requirements by this application. Please pay attention to the warnings listed below and check if your application will use the corresponding features.
+
+
+
+ Congratulations! Your server configuration satisfies all requirements.
+
+
-
Details
+
Details
-
-
Name
Result
Required By
Memo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
Name
Result
Required By
Memo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
diff --git a/framework/rest/Action.php b/framework/rest/Action.php
index 1a985397b7d..9c9d8cd2986 100644
--- a/framework/rest/Action.php
+++ b/framework/rest/Action.php
@@ -20,87 +20,86 @@
*/
class Action extends \yii\base\Action
{
- /**
- * @var string class name of the model which will be handled by this action.
- * The model class must implement [[ActiveRecordInterface]].
- * This property must be set.
- */
- public $modelClass;
- /**
- * @var callable a PHP callable that will be called to return the model corresponding
- * to the specified primary key value. If not set, [[findModel()]] will be used instead.
- * The signature of the callable should be:
- *
- * ```php
- * function ($id, $action) {
- * // $id is the primary key value. If composite primary key, the key values
- * // will be separated by comma.
- * // $action is the action object currently running
- * }
- * ```
- *
- * The callable should return the model found, or throw an exception if not found.
- */
- public $findModel;
- /**
- * @var callable a PHP callable that will be called when running an action to determine
- * if the current user has the permission to execute the action. If not set, the access
- * check will not be performed. The signature of the callable should be as follows,
- *
- * ```php
- * function ($action, $model = null) {
- * // $model is the requested model instance.
- * // If null, it means no specific model (e.g. IndexAction)
- * }
- * ```
- */
- public $checkAccess;
+ /**
+ * @var string class name of the model which will be handled by this action.
+ * The model class must implement [[ActiveRecordInterface]].
+ * This property must be set.
+ */
+ public $modelClass;
+ /**
+ * @var callable a PHP callable that will be called to return the model corresponding
+ * to the specified primary key value. If not set, [[findModel()]] will be used instead.
+ * The signature of the callable should be:
+ *
+ * ```php
+ * function ($id, $action) {
+ * // $id is the primary key value. If composite primary key, the key values
+ * // will be separated by comma.
+ * // $action is the action object currently running
+ * }
+ * ```
+ *
+ * The callable should return the model found, or throw an exception if not found.
+ */
+ public $findModel;
+ /**
+ * @var callable a PHP callable that will be called when running an action to determine
+ * if the current user has the permission to execute the action. If not set, the access
+ * check will not be performed. The signature of the callable should be as follows,
+ *
+ * ```php
+ * function ($action, $model = null) {
+ * // $model is the requested model instance.
+ * // If null, it means no specific model (e.g. IndexAction)
+ * }
+ * ```
+ */
+ public $checkAccess;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ if ($this->modelClass === null) {
+ throw new InvalidConfigException(get_class($this) . '::$modelClass must be set.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- if ($this->modelClass === null) {
- throw new InvalidConfigException(get_class($this) . '::$modelClass must be set.');
- }
- }
+ /**
+ * Returns the data model based on the primary key given.
+ * If the data model is not found, a 404 HTTP exception will be raised.
+ * @param string $id the ID of the model to be loaded. If the model has a composite primary key,
+ * the ID must be a string of the primary key values separated by commas.
+ * The order of the primary key values should follow that returned by the `primaryKey()` method
+ * of the model.
+ * @return ActiveRecordInterface the model found
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ public function findModel($id)
+ {
+ if ($this->findModel !== null) {
+ return call_user_func($this->findModel, $id, $this);
+ }
- /**
- * Returns the data model based on the primary key given.
- * If the data model is not found, a 404 HTTP exception will be raised.
- * @param string $id the ID of the model to be loaded. If the model has a composite primary key,
- * the ID must be a string of the primary key values separated by commas.
- * The order of the primary key values should follow that returned by the `primaryKey()` method
- * of the model.
- * @return ActiveRecordInterface the model found
- * @throws NotFoundHttpException if the model cannot be found
- */
- public function findModel($id)
- {
- if ($this->findModel !== null) {
- return call_user_func($this->findModel, $id, $this);
- }
+ /**
+ * @var ActiveRecordInterface $modelClass
+ */
+ $modelClass = $this->modelClass;
+ $keys = $modelClass::primaryKey();
+ if (count($keys) > 1) {
+ $values = explode(',', $id);
+ if (count($keys) === count($values)) {
+ $model = $modelClass::find(array_combine($keys, $values));
+ }
+ } elseif ($id !== null) {
+ $model = $modelClass::find($id);
+ }
- /**
- * @var ActiveRecordInterface $modelClass
- */
- $modelClass = $this->modelClass;
- $keys = $modelClass::primaryKey();
- if (count($keys) > 1) {
- $values = explode(',', $id);
- if (count($keys) === count($values)) {
- $model = $modelClass::find(array_combine($keys, $values));
- }
- } elseif ($id !== null) {
- $model = $modelClass::find($id);
- }
-
- if (isset($model)) {
- return $model;
- } else {
- throw new NotFoundHttpException("Object not found: $id");
- }
- }
+ if (isset($model)) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException("Object not found: $id");
+ }
+ }
}
diff --git a/framework/rest/ActiveController.php b/framework/rest/ActiveController.php
index 75a4f556cd8..037ae4b3cde 100644
--- a/framework/rest/ActiveController.php
+++ b/framework/rest/ActiveController.php
@@ -36,91 +36,90 @@
*/
class ActiveController extends Controller
{
- /**
- * @var string the model class name. This property must be set.
- */
- public $modelClass;
- /**
- * @var string the scenario used for updating a model.
- * @see \yii\base\Model::scenarios()
- */
- public $updateScenario = Model::SCENARIO_DEFAULT;
- /**
- * @var string the scenario used for creating a model.
- * @see \yii\base\Model::scenarios()
- */
- public $createScenario = Model::SCENARIO_DEFAULT;
- /**
- * @var boolean whether to use a DB transaction when creating, updating or deleting a model.
- * This property is only useful for relational database.
- */
- public $transactional = true;
+ /**
+ * @var string the model class name. This property must be set.
+ */
+ public $modelClass;
+ /**
+ * @var string the scenario used for updating a model.
+ * @see \yii\base\Model::scenarios()
+ */
+ public $updateScenario = Model::SCENARIO_DEFAULT;
+ /**
+ * @var string the scenario used for creating a model.
+ * @see \yii\base\Model::scenarios()
+ */
+ public $createScenario = Model::SCENARIO_DEFAULT;
+ /**
+ * @var boolean whether to use a DB transaction when creating, updating or deleting a model.
+ * This property is only useful for relational database.
+ */
+ public $transactional = true;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->modelClass === null) {
+ throw new InvalidConfigException('The "modelClass" property must be set.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->modelClass === null) {
- throw new InvalidConfigException('The "modelClass" property must be set.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function actions()
+ {
+ return [
+ 'index' => [
+ 'class' => 'yii\rest\IndexAction',
+ 'modelClass' => $this->modelClass,
+ 'checkAccess' => [$this, 'checkAccess'],
+ ],
+ 'view' => [
+ 'class' => 'yii\rest\ViewAction',
+ 'modelClass' => $this->modelClass,
+ 'checkAccess' => [$this, 'checkAccess'],
+ ],
+ 'create' => [
+ 'class' => 'yii\rest\CreateAction',
+ 'modelClass' => $this->modelClass,
+ 'checkAccess' => [$this, 'checkAccess'],
+ 'scenario' => $this->createScenario,
+ 'transactional' => $this->transactional,
+ ],
+ 'update' => [
+ 'class' => 'yii\rest\UpdateAction',
+ 'modelClass' => $this->modelClass,
+ 'checkAccess' => [$this, 'checkAccess'],
+ 'scenario' => $this->updateScenario,
+ 'transactional' => $this->transactional,
+ ],
+ 'delete' => [
+ 'class' => 'yii\rest\DeleteAction',
+ 'modelClass' => $this->modelClass,
+ 'checkAccess' => [$this, 'checkAccess'],
+ 'transactional' => $this->transactional,
+ ],
+ 'options' => [
+ 'class' => 'yii\rest\OptionsAction',
+ ],
+ ];
+ }
- /**
- * @inheritdoc
- */
- public function actions()
- {
- return [
- 'index' => [
- 'class' => 'yii\rest\IndexAction',
- 'modelClass' => $this->modelClass,
- 'checkAccess' => [$this, 'checkAccess'],
- ],
- 'view' => [
- 'class' => 'yii\rest\ViewAction',
- 'modelClass' => $this->modelClass,
- 'checkAccess' => [$this, 'checkAccess'],
- ],
- 'create' => [
- 'class' => 'yii\rest\CreateAction',
- 'modelClass' => $this->modelClass,
- 'checkAccess' => [$this, 'checkAccess'],
- 'scenario' => $this->createScenario,
- 'transactional' => $this->transactional,
- ],
- 'update' => [
- 'class' => 'yii\rest\UpdateAction',
- 'modelClass' => $this->modelClass,
- 'checkAccess' => [$this, 'checkAccess'],
- 'scenario' => $this->updateScenario,
- 'transactional' => $this->transactional,
- ],
- 'delete' => [
- 'class' => 'yii\rest\DeleteAction',
- 'modelClass' => $this->modelClass,
- 'checkAccess' => [$this, 'checkAccess'],
- 'transactional' => $this->transactional,
- ],
- 'options' => [
- 'class' => 'yii\rest\OptionsAction',
- ],
- ];
- }
-
- /**
- * @inheritdoc
- */
- protected function verbs()
- {
- return [
- 'index' => ['GET', 'HEAD'],
- 'view' => ['GET', 'HEAD'],
- 'create' => ['POST'],
- 'update' => ['PUT', 'PATCH'],
- 'delete' => ['DELETE'],
- ];
- }
+ /**
+ * @inheritdoc
+ */
+ protected function verbs()
+ {
+ return [
+ 'index' => ['GET', 'HEAD'],
+ 'view' => ['GET', 'HEAD'],
+ 'create' => ['POST'],
+ 'update' => ['PUT', 'PATCH'],
+ 'delete' => ['DELETE'],
+ ];
+ }
}
diff --git a/framework/rest/AuthInterface.php b/framework/rest/AuthInterface.php
index 30ccc9f1e4e..da7a22d6779 100644
--- a/framework/rest/AuthInterface.php
+++ b/framework/rest/AuthInterface.php
@@ -21,21 +21,21 @@
*/
interface AuthInterface
{
- /**
- * Authenticates the current user.
- *
- * @param User $user
- * @param Request $request
- * @param Response $response
- * @return IdentityInterface the authenticated user identity. If authentication information is not provided, null will be returned.
- * @throws UnauthorizedHttpException if authentication information is provided but is invalid.
- */
- public function authenticate($user, $request, $response);
- /**
- * Handles authentication failure.
- * The implementation should normally throw UnauthorizedHttpException to indicate authentication failure.
- * @param Response $response
- * @throws UnauthorizedHttpException
- */
- public function handleFailure($response);
+ /**
+ * Authenticates the current user.
+ *
+ * @param User $user
+ * @param Request $request
+ * @param Response $response
+ * @return IdentityInterface the authenticated user identity. If authentication information is not provided, null will be returned.
+ * @throws UnauthorizedHttpException if authentication information is provided but is invalid.
+ */
+ public function authenticate($user, $request, $response);
+ /**
+ * Handles authentication failure.
+ * The implementation should normally throw UnauthorizedHttpException to indicate authentication failure.
+ * @param Response $response
+ * @throws UnauthorizedHttpException
+ */
+ public function handleFailure($response);
}
diff --git a/framework/rest/Controller.php b/framework/rest/Controller.php
index 7900b15f908..3843a0db5da 100644
--- a/framework/rest/Controller.php
+++ b/framework/rest/Controller.php
@@ -31,217 +31,220 @@
*/
class Controller extends \yii\web\Controller
{
- /**
- * @var string the name of the header parameter representing the API version number.
- */
- public $versionHeaderParam = 'version';
- /**
- * @var string|array the configuration for creating the serializer that formats the response data.
- */
- public $serializer = 'yii\rest\Serializer';
- /**
- * @inheritdoc
- */
- public $enableCsrfValidation = false;
- /**
- * @var array the supported authentication methods. This property should take a list of supported
- * authentication methods, each represented by an authentication class or configuration.
- * If this is not set or empty, it means authentication is disabled.
- */
- public $authMethods;
- /**
- * @var string|array the rate limiter class or configuration. If this is not set or empty,
- * the rate limiting will be disabled. Note that if the user is not authenticated, the rate limiting
- * will also NOT be performed.
- * @see checkRateLimit()
- * @see authMethods
- */
- public $rateLimiter = 'yii\rest\RateLimiter';
- /**
- * @var string the chosen API version number, or null if [[supportedVersions]] is empty.
- * @see supportedVersions
- */
- public $version;
- /**
- * @var array list of supported API version numbers. If the current request does not specify a version
- * number, the first element will be used as the [[version|chosen version number]]. For this reason, you should
- * put the latest version number at the first. If this property is empty, [[version]] will not be set.
- */
- public $supportedVersions = [];
- /**
- * @var array list of supported response formats. The array keys are the requested content MIME types,
- * and the array values are the corresponding response formats. The first element will be used
- * as the response format if the current request does not specify a content type.
- */
- public $supportedFormats = [
- 'application/json' => Response::FORMAT_JSON,
- 'application/xml' => Response::FORMAT_XML,
- ];
-
- /**
- * @inheritdoc
- */
- public function behaviors()
- {
- return [
- 'verbFilter' => [
- 'class' => VerbFilter::className(),
- 'actions' => $this->verbs(),
- ],
- ];
- }
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- $this->resolveFormatAndVersion();
- }
-
- /**
- * @inheritdoc
- */
- public function beforeAction($action)
- {
- if (parent::beforeAction($action)) {
- $this->authenticate();
- $this->checkRateLimit($action);
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * @inheritdoc
- */
- public function afterAction($action, $result)
- {
- $result = parent::afterAction($action, $result);
- return $this->serializeData($result);
- }
-
- /**
- * Resolves the response format and the API version number.
- * @throws UnsupportedMediaTypeHttpException
- */
- protected function resolveFormatAndVersion()
- {
- $this->version = empty($this->supportedVersions) ? null : reset($this->supportedVersions);
- Yii::$app->getResponse()->format = reset($this->supportedFormats);
- $types = Yii::$app->getRequest()->getAcceptableContentTypes();
- if (empty($types)) {
- $types['*/*'] = [];
- }
-
- foreach ($types as $type => $params) {
- if (isset($this->supportedFormats[$type])) {
- Yii::$app->getResponse()->format = $this->supportedFormats[$type];
- if (isset($params[$this->versionHeaderParam])) {
- if (in_array($params[$this->versionHeaderParam], $this->supportedVersions, true)) {
- $this->version = $params[$this->versionHeaderParam];
- } else {
- throw new UnsupportedMediaTypeHttpException('You are requesting an invalid version number.');
- }
- }
- return;
- }
- }
-
- if (!isset($types['*/*'])) {
- throw new UnsupportedMediaTypeHttpException('None of your requested content types is supported.');
- }
- }
-
- /**
- * Declares the allowed HTTP verbs.
- * Please refer to [[VerbFilter::actions]] on how to declare the allowed verbs.
- * @return array the allowed HTTP verbs.
- */
- protected function verbs()
- {
- return [];
- }
-
- /**
- * Authenticates the user.
- * This method implements the user authentication based on an access token sent through the `Authorization` HTTP header.
- * @throws UnauthorizedHttpException if the user is not authenticated successfully
- */
- protected function authenticate()
- {
- if (empty($this->authMethods)) {
- return;
- }
-
- $user = Yii::$app->getUser();
- $request = Yii::$app->getRequest();
- $response = Yii::$app->getResponse();
- foreach ($this->authMethods as $i => $auth) {
- $this->authMethods[$i] = $auth = Yii::createObject($auth);
- if (!$auth instanceof AuthInterface) {
- throw new InvalidConfigException(get_class($auth) . ' must implement yii\rest\AuthInterface');
- } elseif ($auth->authenticate($user, $request, $response) !== null) {
- return;
- }
- }
-
- /** @var AuthInterface $auth */
- $auth = reset($this->authMethods);
- $auth->handleFailure($response);
- }
-
- /**
- * Ensures the rate limit is not exceeded.
- *
- * This method will use [[rateLimiter]] to check rate limit. In order to perform rate limiting check,
- * the user must be authenticated and the user identity object (`Yii::$app->user->identity`) must
- * implement [[RateLimitInterface]].
- *
- * @param \yii\base\Action $action the action to be executed
- * @throws TooManyRequestsHttpException if the rate limit is exceeded.
- */
- protected function checkRateLimit($action)
- {
- if (empty($this->rateLimiter)) {
- return;
- }
-
- $identity = Yii::$app->getUser()->getIdentity(false);
- if ($identity instanceof RateLimitInterface) {
- /** @var RateLimiter $rateLimiter */
- $rateLimiter = Yii::createObject($this->rateLimiter);
- $rateLimiter->check($identity, Yii::$app->getRequest(), Yii::$app->getResponse(), $action);
- }
- }
-
- /**
- * Serializes the specified data.
- * The default implementation will create a serializer based on the configuration given by [[serializer]].
- * It then uses the serializer to serialize the given data.
- * @param mixed $data the data to be serialized
- * @return mixed the serialized data.
- */
- protected function serializeData($data)
- {
- return Yii::createObject($this->serializer)->serialize($data);
- }
-
- /**
- * Checks the privilege of the current user.
- *
- * This method should be overridden to check whether the current user has the privilege
- * to run the specified action against the specified data model.
- * If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
- *
- * @param string $action the ID of the action to be executed
- * @param object $model the model to be accessed. If null, it means no specific model is being accessed.
- * @param array $params additional parameters
- * @throws ForbiddenHttpException if the user does not have access
- */
- public function checkAccess($action, $model = null, $params = [])
- {
- }
+ /**
+ * @var string the name of the header parameter representing the API version number.
+ */
+ public $versionHeaderParam = 'version';
+ /**
+ * @var string|array the configuration for creating the serializer that formats the response data.
+ */
+ public $serializer = 'yii\rest\Serializer';
+ /**
+ * @inheritdoc
+ */
+ public $enableCsrfValidation = false;
+ /**
+ * @var array the supported authentication methods. This property should take a list of supported
+ * authentication methods, each represented by an authentication class or configuration.
+ * If this is not set or empty, it means authentication is disabled.
+ */
+ public $authMethods;
+ /**
+ * @var string|array the rate limiter class or configuration. If this is not set or empty,
+ * the rate limiting will be disabled. Note that if the user is not authenticated, the rate limiting
+ * will also NOT be performed.
+ * @see checkRateLimit()
+ * @see authMethods
+ */
+ public $rateLimiter = 'yii\rest\RateLimiter';
+ /**
+ * @var string the chosen API version number, or null if [[supportedVersions]] is empty.
+ * @see supportedVersions
+ */
+ public $version;
+ /**
+ * @var array list of supported API version numbers. If the current request does not specify a version
+ * number, the first element will be used as the [[version|chosen version number]]. For this reason, you should
+ * put the latest version number at the first. If this property is empty, [[version]] will not be set.
+ */
+ public $supportedVersions = [];
+ /**
+ * @var array list of supported response formats. The array keys are the requested content MIME types,
+ * and the array values are the corresponding response formats. The first element will be used
+ * as the response format if the current request does not specify a content type.
+ */
+ public $supportedFormats = [
+ 'application/json' => Response::FORMAT_JSON,
+ 'application/xml' => Response::FORMAT_XML,
+ ];
+
+ /**
+ * @inheritdoc
+ */
+ public function behaviors()
+ {
+ return [
+ 'verbFilter' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => $this->verbs(),
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ $this->resolveFormatAndVersion();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function beforeAction($action)
+ {
+ if (parent::beforeAction($action)) {
+ $this->authenticate();
+ $this->checkRateLimit($action);
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function afterAction($action, $result)
+ {
+ $result = parent::afterAction($action, $result);
+
+ return $this->serializeData($result);
+ }
+
+ /**
+ * Resolves the response format and the API version number.
+ * @throws UnsupportedMediaTypeHttpException
+ */
+ protected function resolveFormatAndVersion()
+ {
+ $this->version = empty($this->supportedVersions) ? null : reset($this->supportedVersions);
+ Yii::$app->getResponse()->format = reset($this->supportedFormats);
+ $types = Yii::$app->getRequest()->getAcceptableContentTypes();
+ if (empty($types)) {
+ $types['*/*'] = [];
+ }
+
+ foreach ($types as $type => $params) {
+ if (isset($this->supportedFormats[$type])) {
+ Yii::$app->getResponse()->format = $this->supportedFormats[$type];
+ if (isset($params[$this->versionHeaderParam])) {
+ if (in_array($params[$this->versionHeaderParam], $this->supportedVersions, true)) {
+ $this->version = $params[$this->versionHeaderParam];
+ } else {
+ throw new UnsupportedMediaTypeHttpException('You are requesting an invalid version number.');
+ }
+ }
+
+ return;
+ }
+ }
+
+ if (!isset($types['*/*'])) {
+ throw new UnsupportedMediaTypeHttpException('None of your requested content types is supported.');
+ }
+ }
+
+ /**
+ * Declares the allowed HTTP verbs.
+ * Please refer to [[VerbFilter::actions]] on how to declare the allowed verbs.
+ * @return array the allowed HTTP verbs.
+ */
+ protected function verbs()
+ {
+ return [];
+ }
+
+ /**
+ * Authenticates the user.
+ * This method implements the user authentication based on an access token sent through the `Authorization` HTTP header.
+ * @throws UnauthorizedHttpException if the user is not authenticated successfully
+ */
+ protected function authenticate()
+ {
+ if (empty($this->authMethods)) {
+ return;
+ }
+
+ $user = Yii::$app->getUser();
+ $request = Yii::$app->getRequest();
+ $response = Yii::$app->getResponse();
+ foreach ($this->authMethods as $i => $auth) {
+ $this->authMethods[$i] = $auth = Yii::createObject($auth);
+ if (!$auth instanceof AuthInterface) {
+ throw new InvalidConfigException(get_class($auth) . ' must implement yii\rest\AuthInterface');
+ } elseif ($auth->authenticate($user, $request, $response) !== null) {
+ return;
+ }
+ }
+
+ /** @var AuthInterface $auth */
+ $auth = reset($this->authMethods);
+ $auth->handleFailure($response);
+ }
+
+ /**
+ * Ensures the rate limit is not exceeded.
+ *
+ * This method will use [[rateLimiter]] to check rate limit. In order to perform rate limiting check,
+ * the user must be authenticated and the user identity object (`Yii::$app->user->identity`) must
+ * implement [[RateLimitInterface]].
+ *
+ * @param \yii\base\Action $action the action to be executed
+ * @throws TooManyRequestsHttpException if the rate limit is exceeded.
+ */
+ protected function checkRateLimit($action)
+ {
+ if (empty($this->rateLimiter)) {
+ return;
+ }
+
+ $identity = Yii::$app->getUser()->getIdentity(false);
+ if ($identity instanceof RateLimitInterface) {
+ /** @var RateLimiter $rateLimiter */
+ $rateLimiter = Yii::createObject($this->rateLimiter);
+ $rateLimiter->check($identity, Yii::$app->getRequest(), Yii::$app->getResponse(), $action);
+ }
+ }
+
+ /**
+ * Serializes the specified data.
+ * The default implementation will create a serializer based on the configuration given by [[serializer]].
+ * It then uses the serializer to serialize the given data.
+ * @param mixed $data the data to be serialized
+ * @return mixed the serialized data.
+ */
+ protected function serializeData($data)
+ {
+ return Yii::createObject($this->serializer)->serialize($data);
+ }
+
+ /**
+ * Checks the privilege of the current user.
+ *
+ * This method should be overridden to check whether the current user has the privilege
+ * to run the specified action against the specified data model.
+ * If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
+ *
+ * @param string $action the ID of the action to be executed
+ * @param object $model the model to be accessed. If null, it means no specific model is being accessed.
+ * @param array $params additional parameters
+ * @throws ForbiddenHttpException if the user does not have access
+ */
+ public function checkAccess($action, $model = null, $params = [])
+ {
+ }
}
diff --git a/framework/rest/CreateAction.php b/framework/rest/CreateAction.php
index 3ef8516d9ef..eb0e2de56be 100644
--- a/framework/rest/CreateAction.php
+++ b/framework/rest/CreateAction.php
@@ -20,62 +20,61 @@
*/
class CreateAction extends Action
{
- /**
- * @var string the scenario to be assigned to the new model before it is validated and saved.
- */
- public $scenario = Model::SCENARIO_DEFAULT;
- /**
- * @var boolean whether to start a DB transaction when saving the model.
- */
- public $transactional = true;
- /**
- * @var string the name of the view action. This property is need to create the URL when the mode is successfully created.
- */
- public $viewAction = 'view';
+ /**
+ * @var string the scenario to be assigned to the new model before it is validated and saved.
+ */
+ public $scenario = Model::SCENARIO_DEFAULT;
+ /**
+ * @var boolean whether to start a DB transaction when saving the model.
+ */
+ public $transactional = true;
+ /**
+ * @var string the name of the view action. This property is need to create the URL when the mode is successfully created.
+ */
+ public $viewAction = 'view';
+ /**
+ * Creates a new model.
+ * @return \yii\db\ActiveRecordInterface the model newly created
+ * @throws \Exception if there is any error when creating the model
+ */
+ public function run()
+ {
+ if ($this->checkAccess) {
+ call_user_func($this->checkAccess, $this->id);
+ }
- /**
- * Creates a new model.
- * @return \yii\db\ActiveRecordInterface the model newly created
- * @throws \Exception if there is any error when creating the model
- */
- public function run()
- {
- if ($this->checkAccess) {
- call_user_func($this->checkAccess, $this->id);
- }
+ /**
+ * @var \yii\db\ActiveRecord $model
+ */
+ $model = new $this->modelClass([
+ 'scenario' => $this->scenario,
+ ]);
- /**
- * @var \yii\db\ActiveRecord $model
- */
- $model = new $this->modelClass([
- 'scenario' => $this->scenario,
- ]);
+ $model->load(Yii::$app->getRequest()->getBodyParams(), '');
- $model->load(Yii::$app->getRequest()->getBodyParams(), '');
+ if ($this->transactional && $model instanceof ActiveRecord) {
+ if ($model->validate()) {
+ $transaction = $model->getDb()->beginTransaction();
+ try {
+ $model->insert(false);
+ $transaction->commit();
+ } catch (\Exception $e) {
+ $transaction->rollback();
+ throw $e;
+ }
+ }
+ } else {
+ $model->save();
+ }
- if ($this->transactional && $model instanceof ActiveRecord) {
- if ($model->validate()) {
- $transaction = $model->getDb()->beginTransaction();
- try {
- $model->insert(false);
- $transaction->commit();
- } catch (\Exception $e) {
- $transaction->rollback();
- throw $e;
- }
- }
- } else {
- $model->save();
- }
+ if (!$model->hasErrors()) {
+ $response = Yii::$app->getResponse();
+ $response->setStatusCode(201);
+ $id = implode(',', array_values($model->getPrimaryKey(true)));
+ $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
+ }
- if (!$model->hasErrors()) {
- $response = Yii::$app->getResponse();
- $response->setStatusCode(201);
- $id = implode(',', array_values($model->getPrimaryKey(true)));
- $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
- }
-
- return $model;
- }
+ return $model;
+ }
}
diff --git a/framework/rest/DeleteAction.php b/framework/rest/DeleteAction.php
index a0355c8494c..b2d6d57a7c2 100644
--- a/framework/rest/DeleteAction.php
+++ b/framework/rest/DeleteAction.php
@@ -18,36 +18,35 @@
*/
class DeleteAction extends Action
{
- /**
- * @var boolean whether to start a DB transaction when deleting the model.
- */
- public $transactional = true;
-
-
- /**
- * Deletes a model.
- */
- public function run($id)
- {
- $model = $this->findModel($id);
-
- if ($this->checkAccess) {
- call_user_func($this->checkAccess, $this->id, $model);
- }
-
- if ($this->transactional && $model instanceof ActiveRecord) {
- $transaction = $model->getDb()->beginTransaction();
- try {
- $model->delete();
- $transaction->commit();
- } catch (\Exception $e) {
- $transaction->rollback();
- throw $e;
- }
- } else {
- $model->delete();
- }
-
- Yii::$app->getResponse()->setStatusCode(204);
- }
+ /**
+ * @var boolean whether to start a DB transaction when deleting the model.
+ */
+ public $transactional = true;
+
+ /**
+ * Deletes a model.
+ */
+ public function run($id)
+ {
+ $model = $this->findModel($id);
+
+ if ($this->checkAccess) {
+ call_user_func($this->checkAccess, $this->id, $model);
+ }
+
+ if ($this->transactional && $model instanceof ActiveRecord) {
+ $transaction = $model->getDb()->beginTransaction();
+ try {
+ $model->delete();
+ $transaction->commit();
+ } catch (\Exception $e) {
+ $transaction->rollback();
+ throw $e;
+ }
+ } else {
+ $model->delete();
+ }
+
+ Yii::$app->getResponse()->setStatusCode(204);
+ }
}
diff --git a/framework/rest/HttpBasicAuth.php b/framework/rest/HttpBasicAuth.php
index 7a69c154855..c50ac35ce37 100644
--- a/framework/rest/HttpBasicAuth.php
+++ b/framework/rest/HttpBasicAuth.php
@@ -19,32 +19,33 @@
*/
class HttpBasicAuth extends Component implements AuthInterface
{
- /**
- * @var string the HTTP authentication realm
- */
- public $realm = 'api';
+ /**
+ * @var string the HTTP authentication realm
+ */
+ public $realm = 'api';
- /**
- * @inheritdoc
- */
- public function authenticate($user, $request, $response)
- {
- if (($accessToken = $request->getAuthUser()) !== null) {
- $identity = $user->loginByAccessToken($accessToken);
- if ($identity !== null) {
- return $identity;
- }
- $this->handleFailure($response);
- }
- return null;
- }
+ /**
+ * @inheritdoc
+ */
+ public function authenticate($user, $request, $response)
+ {
+ if (($accessToken = $request->getAuthUser()) !== null) {
+ $identity = $user->loginByAccessToken($accessToken);
+ if ($identity !== null) {
+ return $identity;
+ }
+ $this->handleFailure($response);
+ }
- /**
- * @inheritdoc
- */
- public function handleFailure($response)
- {
- $response->getHeaders()->set('WWW-Authenticate', "Basic realm=\"{$this->realm}\"");
- throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
- }
+ return null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function handleFailure($response)
+ {
+ $response->getHeaders()->set('WWW-Authenticate', "Basic realm=\"{$this->realm}\"");
+ throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
+ }
}
diff --git a/framework/rest/HttpBearerAuth.php b/framework/rest/HttpBearerAuth.php
index fa39d63084b..415851ffd45 100644
--- a/framework/rest/HttpBearerAuth.php
+++ b/framework/rest/HttpBearerAuth.php
@@ -19,34 +19,35 @@
*/
class HttpBearerAuth extends Component implements AuthInterface
{
- /**
- * @var string the HTTP authentication realm
- */
- public $realm = 'api';
+ /**
+ * @var string the HTTP authentication realm
+ */
+ public $realm = 'api';
- /**
- * @inheritdoc
- */
- public function authenticate($user, $request, $response)
- {
- $authHeader = $request->getHeaders()->get('Authorization');
- if ($authHeader !== null && preg_match("/^Bearer\\s+(.*?)$/", $authHeader, $matches)) {
- $identity = $user->loginByAccessToken($matches[1]);
- if ($identity !== null) {
- return $identity;
- }
+ /**
+ * @inheritdoc
+ */
+ public function authenticate($user, $request, $response)
+ {
+ $authHeader = $request->getHeaders()->get('Authorization');
+ if ($authHeader !== null && preg_match("/^Bearer\\s+(.*?)$/", $authHeader, $matches)) {
+ $identity = $user->loginByAccessToken($matches[1]);
+ if ($identity !== null) {
+ return $identity;
+ }
- $this->handleFailure($response);
- }
- return null;
- }
+ $this->handleFailure($response);
+ }
- /**
- * @inheritdoc
- */
- public function handleFailure($response)
- {
- $response->getHeaders()->set('WWW-Authenticate', "Bearer realm=\"{$this->realm}\"");
- throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
- }
+ return null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function handleFailure($response)
+ {
+ $response->getHeaders()->set('WWW-Authenticate', "Bearer realm=\"{$this->realm}\"");
+ throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
+ }
}
diff --git a/framework/rest/IndexAction.php b/framework/rest/IndexAction.php
index ca30220a08a..1ca473b1989 100644
--- a/framework/rest/IndexAction.php
+++ b/framework/rest/IndexAction.php
@@ -16,50 +16,50 @@
*/
class IndexAction extends Action
{
- /**
- * @var callable a PHP callable that will be called to prepare a data provider that
- * should return a collection of the models. If not set, [[prepareDataProvider()]] will be used instead.
- * The signature of the callable should be:
- *
- * ```php
- * function ($action) {
- * // $action is the action object currently running
- * }
- * ```
- *
- * The callable should return an instance of [[ActiveDataProvider]].
- */
- public $prepareDataProvider;
+ /**
+ * @var callable a PHP callable that will be called to prepare a data provider that
+ * should return a collection of the models. If not set, [[prepareDataProvider()]] will be used instead.
+ * The signature of the callable should be:
+ *
+ * ```php
+ * function ($action) {
+ * // $action is the action object currently running
+ * }
+ * ```
+ *
+ * The callable should return an instance of [[ActiveDataProvider]].
+ */
+ public $prepareDataProvider;
+ /**
+ * @return ActiveDataProvider
+ */
+ public function run()
+ {
+ if ($this->checkAccess) {
+ call_user_func($this->checkAccess, $this->id);
+ }
- /**
- * @return ActiveDataProvider
- */
- public function run()
- {
- if ($this->checkAccess) {
- call_user_func($this->checkAccess, $this->id);
- }
+ return $this->prepareDataProvider();
+ }
- return $this->prepareDataProvider();
- }
+ /**
+ * Prepares the data provider that should return the requested collection of the models.
+ * @return ActiveDataProvider
+ */
+ protected function prepareDataProvider()
+ {
+ if ($this->prepareDataProvider !== null) {
+ return call_user_func($this->prepareDataProvider, $this);
+ }
- /**
- * Prepares the data provider that should return the requested collection of the models.
- * @return ActiveDataProvider
- */
- protected function prepareDataProvider()
- {
- if ($this->prepareDataProvider !== null) {
- return call_user_func($this->prepareDataProvider, $this);
- }
+ /**
+ * @var \yii\db\BaseActiveRecord $modelClass
+ */
+ $modelClass = $this->modelClass;
- /**
- * @var \yii\db\BaseActiveRecord $modelClass
- */
- $modelClass = $this->modelClass;
- return new ActiveDataProvider([
- 'query' => $modelClass::find(),
- ]);
- }
+ return new ActiveDataProvider([
+ 'query' => $modelClass::find(),
+ ]);
+ }
}
diff --git a/framework/rest/OptionsAction.php b/framework/rest/OptionsAction.php
index 0f9561fd75f..ad466c1d045 100644
--- a/framework/rest/OptionsAction.php
+++ b/framework/rest/OptionsAction.php
@@ -17,26 +17,25 @@
*/
class OptionsAction extends \yii\base\Action
{
- /**
- * @var array the HTTP verbs that are supported by the collection URL
- */
- public $collectionOptions = ['GET', 'POST', 'HEAD', 'OPTIONS'];
- /**
- * @var array the HTTP verbs that are supported by the resource URL
- */
- public $resourceOptions = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
+ /**
+ * @var array the HTTP verbs that are supported by the collection URL
+ */
+ public $collectionOptions = ['GET', 'POST', 'HEAD', 'OPTIONS'];
+ /**
+ * @var array the HTTP verbs that are supported by the resource URL
+ */
+ public $resourceOptions = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
-
- /**
- * Responds to the OPTIONS request.
- * @param string $id
- */
- public function run($id = null)
- {
- if (Yii::$app->getRequest()->getMethod() !== 'OPTIONS') {
- Yii::$app->getResponse()->setStatusCode(405);
- }
- $options = $id === null ? $this->collectionOptions : $this->resourceOptions;
- Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $options));
- }
+ /**
+ * Responds to the OPTIONS request.
+ * @param string $id
+ */
+ public function run($id = null)
+ {
+ if (Yii::$app->getRequest()->getMethod() !== 'OPTIONS') {
+ Yii::$app->getResponse()->setStatusCode(405);
+ }
+ $options = $id === null ? $this->collectionOptions : $this->resourceOptions;
+ Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $options));
+ }
}
diff --git a/framework/rest/QueryParamAuth.php b/framework/rest/QueryParamAuth.php
index f45e4c8c3ef..ff50c8cdec7 100644
--- a/framework/rest/QueryParamAuth.php
+++ b/framework/rest/QueryParamAuth.php
@@ -19,34 +19,35 @@
*/
class QueryParamAuth extends Component implements AuthInterface
{
- /**
- * @var string the parameter name for passing the access token
- */
- public $tokenParam = 'access-token';
+ /**
+ * @var string the parameter name for passing the access token
+ */
+ public $tokenParam = 'access-token';
- /**
- * @inheritdoc
- */
- public function authenticate($user, $request, $response)
- {
- $accessToken = $request->get($this->tokenParam);
- if (is_string($accessToken)) {
- $identity = $user->loginByAccessToken($accessToken);
- if ($identity !== null) {
- return $identity;
- }
- }
- if ($accessToken !== null) {
- $this->handleFailure($response);
- }
- return null;
- }
+ /**
+ * @inheritdoc
+ */
+ public function authenticate($user, $request, $response)
+ {
+ $accessToken = $request->get($this->tokenParam);
+ if (is_string($accessToken)) {
+ $identity = $user->loginByAccessToken($accessToken);
+ if ($identity !== null) {
+ return $identity;
+ }
+ }
+ if ($accessToken !== null) {
+ $this->handleFailure($response);
+ }
- /**
- * @inheritdoc
- */
- public function handleFailure($response)
- {
- throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
- }
+ return null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function handleFailure($response)
+ {
+ throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
+ }
}
diff --git a/framework/rest/RateLimitInterface.php b/framework/rest/RateLimitInterface.php
index 07f60e02704..72150a1c360 100644
--- a/framework/rest/RateLimitInterface.php
+++ b/framework/rest/RateLimitInterface.php
@@ -15,25 +15,25 @@
*/
interface RateLimitInterface
{
- /**
- * Returns the maximum number of allowed requests and the window size.
- * @param array $params the additional parameters associated with the rate limit.
- * @return array an array of two elements. The first element is the maximum number of allowed requests,
- * and the second element is the size of the window in seconds.
- */
- public function getRateLimit($params = []);
- /**
- * Loads the number of allowed requests and the corresponding timestamp from a persistent storage.
- * @param array $params the additional parameters associated with the rate limit.
- * @return array an array of two elements. The first element is the number of allowed requests,
- * and the second element is the corresponding UNIX timestamp.
- */
- public function loadAllowance($params = []);
- /**
- * Saves the number of allowed requests and the corresponding timestamp to a persistent storage.
- * @param integer $allowance the number of allowed requests remaining.
- * @param integer $timestamp the current timestamp.
- * @param array $params the additional parameters associated with the rate limit.
- */
- public function saveAllowance($allowance, $timestamp, $params = []);
+ /**
+ * Returns the maximum number of allowed requests and the window size.
+ * @param array $params the additional parameters associated with the rate limit.
+ * @return array an array of two elements. The first element is the maximum number of allowed requests,
+ * and the second element is the size of the window in seconds.
+ */
+ public function getRateLimit($params = []);
+ /**
+ * Loads the number of allowed requests and the corresponding timestamp from a persistent storage.
+ * @param array $params the additional parameters associated with the rate limit.
+ * @return array an array of two elements. The first element is the number of allowed requests,
+ * and the second element is the corresponding UNIX timestamp.
+ */
+ public function loadAllowance($params = []);
+ /**
+ * Saves the number of allowed requests and the corresponding timestamp to a persistent storage.
+ * @param integer $allowance the number of allowed requests remaining.
+ * @param integer $timestamp the current timestamp.
+ * @param array $params the additional parameters associated with the rate limit.
+ */
+ public function saveAllowance($allowance, $timestamp, $params = []);
}
diff --git a/framework/rest/RateLimiter.php b/framework/rest/RateLimiter.php
index 753a0f0c67b..a6b890a5df6 100644
--- a/framework/rest/RateLimiter.php
+++ b/framework/rest/RateLimiter.php
@@ -23,63 +23,63 @@
*/
class RateLimiter extends Component
{
- /**
- * @var boolean whether to include rate limit headers in the response
- */
- public $enableRateLimitHeaders = true;
- /**
- * @var string the message to be displayed when rate limit exceeds
- */
- public $errorMessage = 'Rate limit exceeded.';
+ /**
+ * @var boolean whether to include rate limit headers in the response
+ */
+ public $enableRateLimitHeaders = true;
+ /**
+ * @var string the message to be displayed when rate limit exceeds
+ */
+ public $errorMessage = 'Rate limit exceeded.';
- /**
- * Checks whether the rate limit exceeds.
- * @param RateLimitInterface $user the current user
- * @param Request $request
- * @param Response $response
- * @param Action $action the action to be executed
- * @throws TooManyRequestsHttpException if rate limit exceeds
- */
- public function check($user, $request, $response, $action)
- {
- $current = time();
- $params = [
- 'request' => $request,
- 'action' => $action,
- ];
+ /**
+ * Checks whether the rate limit exceeds.
+ * @param RateLimitInterface $user the current user
+ * @param Request $request
+ * @param Response $response
+ * @param Action $action the action to be executed
+ * @throws TooManyRequestsHttpException if rate limit exceeds
+ */
+ public function check($user, $request, $response, $action)
+ {
+ $current = time();
+ $params = [
+ 'request' => $request,
+ 'action' => $action,
+ ];
- list ($limit, $window) = $user->getRateLimit($params);
- list ($allowance, $timestamp) = $user->loadAllowance($params);
+ list ($limit, $window) = $user->getRateLimit($params);
+ list ($allowance, $timestamp) = $user->loadAllowance($params);
- $allowance += (int)(($current - $timestamp) * $limit / $window);
- if ($allowance > $limit) {
- $allowance = $limit;
- }
+ $allowance += (int) (($current - $timestamp) * $limit / $window);
+ if ($allowance > $limit) {
+ $allowance = $limit;
+ }
- if ($allowance < 1) {
- $user->saveAllowance(0, $current, $params);
- $this->addRateLimitHeaders($response, $limit, 0, $window);
- throw new TooManyRequestsHttpException($this->errorMessage);
- } else {
- $user->saveAllowance($allowance - 1, $current, $params);
- $this->addRateLimitHeaders($response, $limit, 0, (int)(($limit - $allowance) * $window / $limit));
- }
- }
+ if ($allowance < 1) {
+ $user->saveAllowance(0, $current, $params);
+ $this->addRateLimitHeaders($response, $limit, 0, $window);
+ throw new TooManyRequestsHttpException($this->errorMessage);
+ } else {
+ $user->saveAllowance($allowance - 1, $current, $params);
+ $this->addRateLimitHeaders($response, $limit, 0, (int) (($limit - $allowance) * $window / $limit));
+ }
+ }
- /**
- * Adds the rate limit headers to the response
- * @param Response $response
- * @param integer $limit the maximum number of allowed requests during a period
- * @param integer $remaining the remaining number of allowed requests within the current period
- * @param integer $reset the number of seconds to wait before having maximum number of allowed requests again
- */
- protected function addRateLimitHeaders($response, $limit, $remaining, $reset)
- {
- if ($this->enableRateLimitHeaders) {
- $response->getHeaders()
- ->set('X-Rate-Limit-Limit', $limit)
- ->set('X-Rate-Limit-Remaining', $remaining)
- ->set('X-Rate-Limit-Reset', $reset);
- }
- }
+ /**
+ * Adds the rate limit headers to the response
+ * @param Response $response
+ * @param integer $limit the maximum number of allowed requests during a period
+ * @param integer $remaining the remaining number of allowed requests within the current period
+ * @param integer $reset the number of seconds to wait before having maximum number of allowed requests again
+ */
+ protected function addRateLimitHeaders($response, $limit, $remaining, $reset)
+ {
+ if ($this->enableRateLimitHeaders) {
+ $response->getHeaders()
+ ->set('X-Rate-Limit-Limit', $limit)
+ ->set('X-Rate-Limit-Remaining', $remaining)
+ ->set('X-Rate-Limit-Reset', $reset);
+ }
+ }
}
diff --git a/framework/rest/Serializer.php b/framework/rest/Serializer.php
index 2a63af06e3c..264adedc214 100644
--- a/framework/rest/Serializer.php
+++ b/framework/rest/Serializer.php
@@ -31,237 +31,241 @@
*/
class Serializer extends Component
{
- /**
- * @var string the name of the query parameter containing the information about which fields should be returned
- * for a [[Model]] object. If the parameter is not provided or empty, the default set of fields as defined
- * by [[Model::fields()]] will be returned.
- */
- public $fieldsParam = 'fields';
- /**
- * @var string the name of the query parameter containing the information about which fields should be returned
- * in addition to those listed in [[fieldsParam]] for a resource object.
- */
- public $expandParam = 'expand';
- /**
- * @var string the name of the HTTP header containing the information about total number of data items.
- * This is used when serving a resource collection with pagination.
- */
- public $totalCountHeader = 'X-Pagination-Total-Count';
- /**
- * @var string the name of the HTTP header containing the information about total number of pages of data.
- * This is used when serving a resource collection with pagination.
- */
- public $pageCountHeader = 'X-Pagination-Page-Count';
- /**
- * @var string the name of the HTTP header containing the information about the current page number (1-based).
- * This is used when serving a resource collection with pagination.
- */
- public $currentPageHeader = 'X-Pagination-Current-Page';
- /**
- * @var string the name of the HTTP header containing the information about the number of data items in each page.
- * This is used when serving a resource collection with pagination.
- */
- public $perPageHeader = 'X-Pagination-Per-Page';
- /**
- * @var string the name of the envelope (e.g. `items`) for returning the resource objects in a collection.
- * This is used when serving a resource collection. When this is set and pagination is enabled, the serializer
- * will return a collection in the following format:
- *
- * ```php
- * [
- * 'items' => [...], // assuming collectionEnvelope is "items"
- * '_links' => { // pagination links as returned by Pagination::getLinks()
- * 'self' => '...',
- * 'next' => '...',
- * 'last' => '...',
- * },
- * '_meta' => { // meta information as returned by Pagination::toArray()
- * 'totalCount' => 100,
- * 'pageCount' => 5,
- * 'currentPage' => 1,
- * 'perPage' => 20,
- * },
- * ]
- * ```
- *
- * If this property is not set, the resource arrays will be directly returned without using envelope.
- * The pagination information as shown in `_links` and `_meta` can be accessed from the response HTTP headers.
- */
- public $collectionEnvelope;
- /**
- * @var Request the current request. If not set, the `request` application component will be used.
- */
- public $request;
- /**
- * @var Response the response to be sent. If not set, the `response` application component will be used.
- */
- public $response;
+ /**
+ * @var string the name of the query parameter containing the information about which fields should be returned
+ * for a [[Model]] object. If the parameter is not provided or empty, the default set of fields as defined
+ * by [[Model::fields()]] will be returned.
+ */
+ public $fieldsParam = 'fields';
+ /**
+ * @var string the name of the query parameter containing the information about which fields should be returned
+ * in addition to those listed in [[fieldsParam]] for a resource object.
+ */
+ public $expandParam = 'expand';
+ /**
+ * @var string the name of the HTTP header containing the information about total number of data items.
+ * This is used when serving a resource collection with pagination.
+ */
+ public $totalCountHeader = 'X-Pagination-Total-Count';
+ /**
+ * @var string the name of the HTTP header containing the information about total number of pages of data.
+ * This is used when serving a resource collection with pagination.
+ */
+ public $pageCountHeader = 'X-Pagination-Page-Count';
+ /**
+ * @var string the name of the HTTP header containing the information about the current page number (1-based).
+ * This is used when serving a resource collection with pagination.
+ */
+ public $currentPageHeader = 'X-Pagination-Current-Page';
+ /**
+ * @var string the name of the HTTP header containing the information about the number of data items in each page.
+ * This is used when serving a resource collection with pagination.
+ */
+ public $perPageHeader = 'X-Pagination-Per-Page';
+ /**
+ * @var string the name of the envelope (e.g. `items`) for returning the resource objects in a collection.
+ * This is used when serving a resource collection. When this is set and pagination is enabled, the serializer
+ * will return a collection in the following format:
+ *
+ * ```php
+ * [
+ * 'items' => [...], // assuming collectionEnvelope is "items"
+ * '_links' => { // pagination links as returned by Pagination::getLinks()
+ * 'self' => '...',
+ * 'next' => '...',
+ * 'last' => '...',
+ * },
+ * '_meta' => { // meta information as returned by Pagination::toArray()
+ * 'totalCount' => 100,
+ * 'pageCount' => 5,
+ * 'currentPage' => 1,
+ * 'perPage' => 20,
+ * },
+ * ]
+ * ```
+ *
+ * If this property is not set, the resource arrays will be directly returned without using envelope.
+ * The pagination information as shown in `_links` and `_meta` can be accessed from the response HTTP headers.
+ */
+ public $collectionEnvelope;
+ /**
+ * @var Request the current request. If not set, the `request` application component will be used.
+ */
+ public $request;
+ /**
+ * @var Response the response to be sent. If not set, the `response` application component will be used.
+ */
+ public $response;
- /**
- * @inheritdoc
- */
- public function init()
- {
- if ($this->request === null) {
- $this->request = Yii::$app->getRequest();
- }
- if ($this->response === null) {
- $this->response = Yii::$app->getResponse();
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ if ($this->request === null) {
+ $this->request = Yii::$app->getRequest();
+ }
+ if ($this->response === null) {
+ $this->response = Yii::$app->getResponse();
+ }
+ }
- /**
- * Serializes the given data into a format that can be easily turned into other formats.
- * This method mainly converts the objects of recognized types into array representation.
- * It will not do conversion for unknown object types or non-object data.
- * The default implementation will handle [[Model]] and [[DataProviderInterface]].
- * You may override this method to support more object types.
- * @param mixed $data the data to be serialized.
- * @return mixed the converted data.
- */
- public function serialize($data)
- {
- if ($data instanceof Model) {
- return $data->hasErrors() ? $this->serializeModelErrors($data) : $this->serializeModel($data);
- } elseif ($data instanceof DataProviderInterface) {
- return $this->serializeDataProvider($data);
- } else {
- return $data;
- }
- }
+ /**
+ * Serializes the given data into a format that can be easily turned into other formats.
+ * This method mainly converts the objects of recognized types into array representation.
+ * It will not do conversion for unknown object types or non-object data.
+ * The default implementation will handle [[Model]] and [[DataProviderInterface]].
+ * You may override this method to support more object types.
+ * @param mixed $data the data to be serialized.
+ * @return mixed the converted data.
+ */
+ public function serialize($data)
+ {
+ if ($data instanceof Model) {
+ return $data->hasErrors() ? $this->serializeModelErrors($data) : $this->serializeModel($data);
+ } elseif ($data instanceof DataProviderInterface) {
+ return $this->serializeDataProvider($data);
+ } else {
+ return $data;
+ }
+ }
- /**
- * @return array the names of the requested fields. The first element is an array
- * representing the list of default fields requested, while the second element is
- * an array of the extra fields requested in addition to the default fields.
- * @see Model::fields()
- * @see Model::extraFields()
- */
- protected function getRequestedFields()
- {
- $fields = $this->request->get($this->fieldsParam);
- $expand = $this->request->get($this->expandParam);
- return [
- preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY),
- preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY),
- ];
- }
+ /**
+ * @return array the names of the requested fields. The first element is an array
+ * representing the list of default fields requested, while the second element is
+ * an array of the extra fields requested in addition to the default fields.
+ * @see Model::fields()
+ * @see Model::extraFields()
+ */
+ protected function getRequestedFields()
+ {
+ $fields = $this->request->get($this->fieldsParam);
+ $expand = $this->request->get($this->expandParam);
- /**
- * Serializes a data provider.
- * @param DataProviderInterface $dataProvider
- * @return array the array representation of the data provider.
- */
- protected function serializeDataProvider($dataProvider)
- {
- $models = $this->serializeModels($dataProvider->getModels());
+ return [
+ preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY),
+ preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY),
+ ];
+ }
- if (($pagination = $dataProvider->getPagination()) !== false) {
- $this->addPaginationHeaders($pagination);
- }
+ /**
+ * Serializes a data provider.
+ * @param DataProviderInterface $dataProvider
+ * @return array the array representation of the data provider.
+ */
+ protected function serializeDataProvider($dataProvider)
+ {
+ $models = $this->serializeModels($dataProvider->getModels());
- if ($this->request->getIsHead()) {
- return null;
- } elseif ($this->collectionEnvelope === null) {
- return $models;
- } else {
- $result = [
- $this->collectionEnvelope => $models,
- ];
- if ($pagination !== false) {
- return array_merge($result, $this->serializePagination($pagination));
- } else {
- return $result;
- }
- }
- }
+ if (($pagination = $dataProvider->getPagination()) !== false) {
+ $this->addPaginationHeaders($pagination);
+ }
- /**
- * Serializes a pagination into an array.
- * @param Pagination $pagination
- * @return array the array representation of the pagination
- * @see addPaginationHeader()
- */
- protected function serializePagination($pagination)
- {
- return [
- '_links' => Link::serialize($pagination->getLinks(true)),
- '_meta' => [
- 'totalCount' => $pagination->totalCount,
- 'pageCount' => $pagination->getPageCount(),
- 'currentPage' => $pagination->getPage(),
- 'perPage' => $pagination->getPageSize(),
- ],
- ];
- }
+ if ($this->request->getIsHead()) {
+ return null;
+ } elseif ($this->collectionEnvelope === null) {
+ return $models;
+ } else {
+ $result = [
+ $this->collectionEnvelope => $models,
+ ];
+ if ($pagination !== false) {
+ return array_merge($result, $this->serializePagination($pagination));
+ } else {
+ return $result;
+ }
+ }
+ }
- /**
- * Adds HTTP headers about the pagination to the response.
- * @param Pagination $pagination
- */
- protected function addPaginationHeaders($pagination)
- {
- $links = [];
- foreach ($pagination->getLinks(true) as $rel => $url) {
- $links[] = "<$url>; rel=$rel";
- }
+ /**
+ * Serializes a pagination into an array.
+ * @param Pagination $pagination
+ * @return array the array representation of the pagination
+ * @see addPaginationHeader()
+ */
+ protected function serializePagination($pagination)
+ {
+ return [
+ '_links' => Link::serialize($pagination->getLinks(true)),
+ '_meta' => [
+ 'totalCount' => $pagination->totalCount,
+ 'pageCount' => $pagination->getPageCount(),
+ 'currentPage' => $pagination->getPage(),
+ 'perPage' => $pagination->getPageSize(),
+ ],
+ ];
+ }
- $this->response->getHeaders()
- ->set($this->totalCountHeader, $pagination->totalCount)
- ->set($this->pageCountHeader, $pagination->getPageCount())
- ->set($this->currentPageHeader, $pagination->getPage() + 1)
- ->set($this->perPageHeader, $pagination->pageSize)
- ->set('Link', implode(', ', $links));
- }
+ /**
+ * Adds HTTP headers about the pagination to the response.
+ * @param Pagination $pagination
+ */
+ protected function addPaginationHeaders($pagination)
+ {
+ $links = [];
+ foreach ($pagination->getLinks(true) as $rel => $url) {
+ $links[] = "<$url>; rel=$rel";
+ }
- /**
- * Serializes a model object.
- * @param Model $model
- * @return array the array representation of the model
- */
- protected function serializeModel($model)
- {
- if ($this->request->getIsHead()) {
- return null;
- } else {
- list ($fields, $expand) = $this->getRequestedFields();
- return $model->toArray($fields, $expand);
- }
- }
+ $this->response->getHeaders()
+ ->set($this->totalCountHeader, $pagination->totalCount)
+ ->set($this->pageCountHeader, $pagination->getPageCount())
+ ->set($this->currentPageHeader, $pagination->getPage() + 1)
+ ->set($this->perPageHeader, $pagination->pageSize)
+ ->set('Link', implode(', ', $links));
+ }
- /**
- * Serializes the validation errors in a model.
- * @param Model $model
- * @return array the array representation of the errors
- */
- protected function serializeModelErrors($model)
- {
- $this->response->setStatusCode(422, 'Data Validation Failed.');
- $result = [];
- foreach ($model->getFirstErrors() as $name => $message) {
- $result[] = [
- 'field' => $name,
- 'message' => $message,
- ];
- }
- return $result;
- }
+ /**
+ * Serializes a model object.
+ * @param Model $model
+ * @return array the array representation of the model
+ */
+ protected function serializeModel($model)
+ {
+ if ($this->request->getIsHead()) {
+ return null;
+ } else {
+ list ($fields, $expand) = $this->getRequestedFields();
- /**
- * Serializes a set of models.
- * @param array $models
- * @return array the array representation of the models
- */
- protected function serializeModels(array $models)
- {
- list ($fields, $expand) = $this->getRequestedFields();
- foreach ($models as $i => $model) {
- if ($model instanceof Model) {
- $models[$i] = $model->toArray($fields, $expand);
- } elseif (is_array($model)) {
- $models[$i] = ArrayHelper::toArray($model);
- }
- }
- return $models;
- }
+ return $model->toArray($fields, $expand);
+ }
+ }
+
+ /**
+ * Serializes the validation errors in a model.
+ * @param Model $model
+ * @return array the array representation of the errors
+ */
+ protected function serializeModelErrors($model)
+ {
+ $this->response->setStatusCode(422, 'Data Validation Failed.');
+ $result = [];
+ foreach ($model->getFirstErrors() as $name => $message) {
+ $result[] = [
+ 'field' => $name,
+ 'message' => $message,
+ ];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Serializes a set of models.
+ * @param array $models
+ * @return array the array representation of the models
+ */
+ protected function serializeModels(array $models)
+ {
+ list ($fields, $expand) = $this->getRequestedFields();
+ foreach ($models as $i => $model) {
+ if ($model instanceof Model) {
+ $models[$i] = $model->toArray($fields, $expand);
+ } elseif (is_array($model)) {
+ $models[$i] = ArrayHelper::toArray($model);
+ }
+ }
+
+ return $models;
+ }
}
diff --git a/framework/rest/UpdateAction.php b/framework/rest/UpdateAction.php
index 7a14a0a544b..969b076fb8c 100644
--- a/framework/rest/UpdateAction.php
+++ b/framework/rest/UpdateAction.php
@@ -19,49 +19,48 @@
*/
class UpdateAction extends Action
{
- /**
- * @var string the scenario to be assigned to the model before it is validated and updated.
- */
- public $scenario = Model::SCENARIO_DEFAULT;
- /**
- * @var boolean whether to start a DB transaction when saving the model.
- */
- public $transactional = true;
+ /**
+ * @var string the scenario to be assigned to the model before it is validated and updated.
+ */
+ public $scenario = Model::SCENARIO_DEFAULT;
+ /**
+ * @var boolean whether to start a DB transaction when saving the model.
+ */
+ public $transactional = true;
+ /**
+ * Updates an existing model.
+ * @param string $id the primary key of the model.
+ * @return \yii\db\ActiveRecordInterface the model being updated
+ * @throws \Exception if there is any error when updating the model
+ */
+ public function run($id)
+ {
+ /** @var ActiveRecord $model */
+ $model = $this->findModel($id);
- /**
- * Updates an existing model.
- * @param string $id the primary key of the model.
- * @return \yii\db\ActiveRecordInterface the model being updated
- * @throws \Exception if there is any error when updating the model
- */
- public function run($id)
- {
- /** @var ActiveRecord $model */
- $model = $this->findModel($id);
+ if ($this->checkAccess) {
+ call_user_func($this->checkAccess, $this->id, $model);
+ }
- if ($this->checkAccess) {
- call_user_func($this->checkAccess, $this->id, $model);
- }
+ $model->scenario = $this->scenario;
+ $model->load(Yii::$app->getRequest()->getBodyParams(), '');
- $model->scenario = $this->scenario;
- $model->load(Yii::$app->getRequest()->getBodyParams(), '');
+ if ($this->transactional && $model instanceof ActiveRecord) {
+ if ($model->validate()) {
+ $transaction = $model->getDb()->beginTransaction();
+ try {
+ $model->update(false);
+ $transaction->commit();
+ } catch (\Exception $e) {
+ $transaction->rollback();
+ throw $e;
+ }
+ }
+ } else {
+ $model->save();
+ }
- if ($this->transactional && $model instanceof ActiveRecord) {
- if ($model->validate()) {
- $transaction = $model->getDb()->beginTransaction();
- try {
- $model->update(false);
- $transaction->commit();
- } catch (\Exception $e) {
- $transaction->rollback();
- throw $e;
- }
- }
- } else {
- $model->save();
- }
-
- return $model;
- }
+ return $model;
+ }
}
diff --git a/framework/rest/UrlRule.php b/framework/rest/UrlRule.php
index 5e4b218569e..c63ff6e6548 100644
--- a/framework/rest/UrlRule.php
+++ b/framework/rest/UrlRule.php
@@ -59,192 +59,195 @@
*/
class UrlRule extends CompositeUrlRule
{
- /**
- * @var string the common prefix string shared by all patterns.
- */
- public $prefix;
- /**
- * @var string the suffix that will be assigned to [[\yii\web\UrlRule::suffix]] for every generated rule.
- */
- public $suffix;
- /**
- * @var string|array the controller ID (e.g. `user`, `post-comment`) that the rules in this composite rule
- * are dealing with. It should be prefixed with the module ID if the controller is within a module (e.g. `admin/user`).
- *
- * By default, the controller ID will be pluralized automatically when it is put in the patterns of the
- * generated rules. If you want to explicitly specify how the controller ID should appear in the patterns,
- * you may use an array with the array key being as the controller ID in the pattern, and the array value
- * the actual controller ID. For example, `['u' => 'user']`.
- *
- * You may also pass multiple controller IDs as an array. If this is the case, this composite rule will
- * generate applicable URL rules for EVERY specified controller. For example, `['user', 'post']`.
- */
- public $controller;
- /**
- * @var array list of acceptable actions. If not empty, only the actions within this array
- * will have the corresponding URL rules created.
- * @see patterns
- */
- public $only = [];
- /**
- * @var array list of actions that should be excluded. Any action found in this array
- * will NOT have its URL rules created.
- * @see patterns
- */
- public $except = [];
- /**
- * @var array patterns for supporting extra actions in addition to those listed in [[patterns]].
- * The keys are the patterns and the values are the corresponding action IDs.
- * These extra patterns will take precedence over [[patterns]].
- */
- public $extraPatterns = [];
- /**
- * @var array list of tokens that should be replaced for each pattern. The keys are the token names,
- * and the values are the corresponding replacements.
- * @see patterns
- */
- public $tokens = [
- '{id}' => '',
- ];
- /**
- * @var array list of possible patterns and the corresponding actions for creating the URL rules.
- * The keys are the patterns and the values are the corresponding actions.
- * The format of patterns is `Verbs Pattern`, where `Verbs` stands for a list of HTTP verbs separated
- * by comma (without space). If `Verbs` is not specified, it means all verbs are allowed.
- * `Pattern` is optional. It will be prefixed with [[prefix]]/[[controller]]/,
- * and tokens in it will be replaced by [[tokens]].
- */
- public $patterns = [
- 'PUT,PATCH {id}' => 'update',
- 'DELETE {id}' => 'delete',
- 'GET,HEAD {id}' => 'view',
- 'POST' => 'create',
- 'GET,HEAD' => 'index',
- '{id}' => 'options',
- '' => 'options',
- ];
- /**
- * @var array the default configuration for creating each URL rule contained by this rule.
- */
- public $ruleConfig = [
- 'class' => 'yii\web\UrlRule',
- ];
- /**
- * @var boolean whether to automatically pluralize the URL names for controllers.
- * If true, a controller ID will appear in plural form in URLs. For example, `user` controller
- * will appear as `users` in URLs.
- * @see controllers
- */
- public $pluralize = true;
-
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- if (empty($this->controller)) {
- throw new InvalidConfigException('"controller" must be set.');
- }
-
- $controllers = [];
- foreach ((array)$this->controller as $urlName => $controller) {
- if (is_integer($urlName)) {
- $urlName = $this->pluralize ? Inflector::pluralize($controller) : $controller;
- }
- $controllers[$urlName] = $controller;
- }
- $this->controller = $controllers;
-
- $this->prefix = trim($this->prefix, '/');
-
- parent::init();
- }
-
- /**
- * @inheritdoc
- */
- protected function createRules()
- {
- $only = array_flip($this->only);
- $except = array_flip($this->except);
- $patterns = array_merge($this->patterns, $this->extraPatterns);
- $rules = [];
- foreach ($this->controller as $urlName => $controller) {
- $prefix = trim($this->prefix . '/' . $urlName, '/');
- foreach ($patterns as $pattern => $action) {
- if (!isset($except[$action]) && (empty($only) || isset($only[$action]))) {
- $rules[$urlName][] = $this->createRule($pattern, $prefix, $controller . '/' . $action);
- }
- }
- }
- return $rules;
- }
-
- /**
- * Creates a URL rule using the given pattern and action.
- * @param string $pattern
- * @param string $prefix
- * @param string $action
- * @return \yii\web\UrlRuleInterface
- */
- protected function createRule($pattern, $prefix, $action)
- {
- $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
- if (preg_match("/^((?:($verbs),)*($verbs))(?:\\s+(.*))?$/", $pattern, $matches)) {
- $verbs = explode(',', $matches[1]);
- $pattern = isset($matches[4]) ? $matches[4] : '';
- } else {
- $verbs = [];
- }
-
- $config = $this->ruleConfig;
- $config['verb'] = $verbs;
- $config['pattern'] = rtrim($prefix . '/' . strtr($pattern, $this->tokens), '/');
- $config['route'] = $action;
- if (!in_array('GET', $verbs)) {
- $config['mode'] = \yii\web\UrlRule::PARSING_ONLY;
- }
- $config['suffix'] = $this->suffix;
-
- return Yii::createObject($config);
- }
-
- /**
- * @inheritdoc
- */
- public function parseRequest($manager, $request)
- {
- $pathInfo = $request->getPathInfo();
- foreach ($this->rules as $urlName => $rules) {
- if (strpos($pathInfo, $urlName) !== false) {
- foreach ($rules as $rule) {
- /** @var \yii\web\UrlRule $rule */
- if (($result = $rule->parseRequest($manager, $request)) !== false) {
- Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__);
- return $result;
- }
- }
- }
- }
- return false;
- }
-
- /**
- * @inheritdoc
- */
- public function createUrl($manager, $route, $params)
- {
- foreach ($this->controller as $urlName => $controller) {
- if (strpos($route, $controller) !== false) {
- foreach ($this->rules[$urlName] as $rule) {
- /** @var \yii\web\UrlRule $rule */
- if (($url = $rule->createUrl($manager, $route, $params)) !== false) {
- return $url;
- }
- }
- }
- }
- return false;
- }
+ /**
+ * @var string the common prefix string shared by all patterns.
+ */
+ public $prefix;
+ /**
+ * @var string the suffix that will be assigned to [[\yii\web\UrlRule::suffix]] for every generated rule.
+ */
+ public $suffix;
+ /**
+ * @var string|array the controller ID (e.g. `user`, `post-comment`) that the rules in this composite rule
+ * are dealing with. It should be prefixed with the module ID if the controller is within a module (e.g. `admin/user`).
+ *
+ * By default, the controller ID will be pluralized automatically when it is put in the patterns of the
+ * generated rules. If you want to explicitly specify how the controller ID should appear in the patterns,
+ * you may use an array with the array key being as the controller ID in the pattern, and the array value
+ * the actual controller ID. For example, `['u' => 'user']`.
+ *
+ * You may also pass multiple controller IDs as an array. If this is the case, this composite rule will
+ * generate applicable URL rules for EVERY specified controller. For example, `['user', 'post']`.
+ */
+ public $controller;
+ /**
+ * @var array list of acceptable actions. If not empty, only the actions within this array
+ * will have the corresponding URL rules created.
+ * @see patterns
+ */
+ public $only = [];
+ /**
+ * @var array list of actions that should be excluded. Any action found in this array
+ * will NOT have its URL rules created.
+ * @see patterns
+ */
+ public $except = [];
+ /**
+ * @var array patterns for supporting extra actions in addition to those listed in [[patterns]].
+ * The keys are the patterns and the values are the corresponding action IDs.
+ * These extra patterns will take precedence over [[patterns]].
+ */
+ public $extraPatterns = [];
+ /**
+ * @var array list of tokens that should be replaced for each pattern. The keys are the token names,
+ * and the values are the corresponding replacements.
+ * @see patterns
+ */
+ public $tokens = [
+ '{id}' => '',
+ ];
+ /**
+ * @var array list of possible patterns and the corresponding actions for creating the URL rules.
+ * The keys are the patterns and the values are the corresponding actions.
+ * The format of patterns is `Verbs Pattern`, where `Verbs` stands for a list of HTTP verbs separated
+ * by comma (without space). If `Verbs` is not specified, it means all verbs are allowed.
+ * `Pattern` is optional. It will be prefixed with [[prefix]]/[[controller]]/,
+ * and tokens in it will be replaced by [[tokens]].
+ */
+ public $patterns = [
+ 'PUT,PATCH {id}' => 'update',
+ 'DELETE {id}' => 'delete',
+ 'GET,HEAD {id}' => 'view',
+ 'POST' => 'create',
+ 'GET,HEAD' => 'index',
+ '{id}' => 'options',
+ '' => 'options',
+ ];
+ /**
+ * @var array the default configuration for creating each URL rule contained by this rule.
+ */
+ public $ruleConfig = [
+ 'class' => 'yii\web\UrlRule',
+ ];
+ /**
+ * @var boolean whether to automatically pluralize the URL names for controllers.
+ * If true, a controller ID will appear in plural form in URLs. For example, `user` controller
+ * will appear as `users` in URLs.
+ * @see controllers
+ */
+ public $pluralize = true;
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ if (empty($this->controller)) {
+ throw new InvalidConfigException('"controller" must be set.');
+ }
+
+ $controllers = [];
+ foreach ((array) $this->controller as $urlName => $controller) {
+ if (is_integer($urlName)) {
+ $urlName = $this->pluralize ? Inflector::pluralize($controller) : $controller;
+ }
+ $controllers[$urlName] = $controller;
+ }
+ $this->controller = $controllers;
+
+ $this->prefix = trim($this->prefix, '/');
+
+ parent::init();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function createRules()
+ {
+ $only = array_flip($this->only);
+ $except = array_flip($this->except);
+ $patterns = array_merge($this->patterns, $this->extraPatterns);
+ $rules = [];
+ foreach ($this->controller as $urlName => $controller) {
+ $prefix = trim($this->prefix . '/' . $urlName, '/');
+ foreach ($patterns as $pattern => $action) {
+ if (!isset($except[$action]) && (empty($only) || isset($only[$action]))) {
+ $rules[$urlName][] = $this->createRule($pattern, $prefix, $controller . '/' . $action);
+ }
+ }
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Creates a URL rule using the given pattern and action.
+ * @param string $pattern
+ * @param string $prefix
+ * @param string $action
+ * @return \yii\web\UrlRuleInterface
+ */
+ protected function createRule($pattern, $prefix, $action)
+ {
+ $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
+ if (preg_match("/^((?:($verbs),)*($verbs))(?:\\s+(.*))?$/", $pattern, $matches)) {
+ $verbs = explode(',', $matches[1]);
+ $pattern = isset($matches[4]) ? $matches[4] : '';
+ } else {
+ $verbs = [];
+ }
+
+ $config = $this->ruleConfig;
+ $config['verb'] = $verbs;
+ $config['pattern'] = rtrim($prefix . '/' . strtr($pattern, $this->tokens), '/');
+ $config['route'] = $action;
+ if (!in_array('GET', $verbs)) {
+ $config['mode'] = \yii\web\UrlRule::PARSING_ONLY;
+ }
+ $config['suffix'] = $this->suffix;
+
+ return Yii::createObject($config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function parseRequest($manager, $request)
+ {
+ $pathInfo = $request->getPathInfo();
+ foreach ($this->rules as $urlName => $rules) {
+ if (strpos($pathInfo, $urlName) !== false) {
+ foreach ($rules as $rule) {
+ /** @var \yii\web\UrlRule $rule */
+ if (($result = $rule->parseRequest($manager, $request)) !== false) {
+ Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__);
+
+ return $result;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createUrl($manager, $route, $params)
+ {
+ foreach ($this->controller as $urlName => $controller) {
+ if (strpos($route, $controller) !== false) {
+ foreach ($this->rules[$urlName] as $rule) {
+ /** @var \yii\web\UrlRule $rule */
+ if (($url = $rule->createUrl($manager, $route, $params)) !== false) {
+ return $url;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
}
diff --git a/framework/rest/ViewAction.php b/framework/rest/ViewAction.php
index c37522f61ed..daf86570e12 100644
--- a/framework/rest/ViewAction.php
+++ b/framework/rest/ViewAction.php
@@ -17,17 +17,18 @@
*/
class ViewAction extends Action
{
- /**
- * Displays a model.
- * @param string $id the primary key of the model.
- * @return \yii\db\ActiveRecordInterface the model being displayed
- */
- public function run($id)
- {
- $model = $this->findModel($id);
- if ($this->checkAccess) {
- call_user_func($this->checkAccess, $this->id, $model);
- }
- return $model;
- }
+ /**
+ * Displays a model.
+ * @param string $id the primary key of the model.
+ * @return \yii\db\ActiveRecordInterface the model being displayed
+ */
+ public function run($id)
+ {
+ $model = $this->findModel($id);
+ if ($this->checkAccess) {
+ call_user_func($this->checkAccess, $this->id, $model);
+ }
+
+ return $model;
+ }
}
diff --git a/framework/test/ActiveFixture.php b/framework/test/ActiveFixture.php
index 6cb2f8ffa78..ee06eaf01c1 100644
--- a/framework/test/ActiveFixture.php
+++ b/framework/test/ActiveFixture.php
@@ -8,7 +8,6 @@
namespace yii\test;
use Yii;
-use yii\base\ArrayAccessTrait;
use yii\base\InvalidConfigException;
use yii\db\TableSchema;
@@ -32,132 +31,132 @@
*/
class ActiveFixture extends BaseActiveFixture
{
- /**
- * @var string the name of the database table that this fixture is about. If this property is not set,
- * the table name will be determined via [[modelClass]].
- * @see modelClass
- */
- public $tableName;
- /**
- * @var string|boolean the file path or path alias of the data file that contains the fixture data
- * to be returned by [[getData()]]. If this is not set, it will default to `FixturePath/data/TableName.php`,
- * where `FixturePath` stands for the directory containing this fixture class, and `TableName` stands for the
- * name of the table associated with this fixture. You can set this property to be false to prevent loading any data.
- */
- public $dataFile;
- /**
- * @var TableSchema the table schema for the table associated with this fixture
- */
- private $_table;
+ /**
+ * @var string the name of the database table that this fixture is about. If this property is not set,
+ * the table name will be determined via [[modelClass]].
+ * @see modelClass
+ */
+ public $tableName;
+ /**
+ * @var string|boolean the file path or path alias of the data file that contains the fixture data
+ * to be returned by [[getData()]]. If this is not set, it will default to `FixturePath/data/TableName.php`,
+ * where `FixturePath` stands for the directory containing this fixture class, and `TableName` stands for the
+ * name of the table associated with this fixture. You can set this property to be false to prevent loading any data.
+ */
+ public $dataFile;
+ /**
+ * @var TableSchema the table schema for the table associated with this fixture
+ */
+ private $_table;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->modelClass) && !isset($this->tableName)) {
+ throw new InvalidConfigException('Either "modelClass" or "tableName" must be set.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if (!isset($this->modelClass) && !isset($this->tableName)) {
- throw new InvalidConfigException('Either "modelClass" or "tableName" must be set.');
- }
- }
+ /**
+ * Loads the fixture.
+ *
+ * It will then populate the table with the data returned by [[getData()]].
+ *
+ * If you override this method, you should consider calling the parent implementation
+ * so that the data returned by [[getData()]] can be populated into the table.
+ */
+ public function load()
+ {
+ parent::load();
- /**
- * Loads the fixture.
- *
- * It will then populate the table with the data returned by [[getData()]].
- *
- * If you override this method, you should consider calling the parent implementation
- * so that the data returned by [[getData()]] can be populated into the table.
- */
- public function load()
- {
- parent::load();
+ $table = $this->getTableSchema();
- $table = $this->getTableSchema();
+ foreach ($this->getData() as $alias => $row) {
+ $this->db->createCommand()->insert($table->fullName, $row)->execute();
+ if ($table->sequenceName !== null) {
+ foreach ($table->primaryKey as $pk) {
+ if (!isset($row[$pk])) {
+ $row[$pk] = $this->db->getLastInsertID($table->sequenceName);
+ break;
+ }
+ }
+ }
+ $this->data[$alias] = $row;
+ }
+ }
- foreach ($this->getData() as $alias => $row) {
- $this->db->createCommand()->insert($table->fullName, $row)->execute();
- if ($table->sequenceName !== null) {
- foreach ($table->primaryKey as $pk) {
- if (!isset($row[$pk])) {
- $row[$pk] = $this->db->getLastInsertID($table->sequenceName);
- break;
- }
- }
- }
- $this->data[$alias] = $row;
- }
- }
+ /**
+ * Unloads the fixture.
+ *
+ * The default implementation will clean up the table by calling [[resetTable()]].
+ */
+ public function unload()
+ {
+ $this->resetTable();
+ parent::unload();
+ }
- /**
- * Unloads the fixture.
- *
- * The default implementation will clean up the table by calling [[resetTable()]].
- */
- public function unload()
- {
- $this->resetTable();
- parent::unload();
- }
+ /**
+ * Returns the fixture data.
+ *
+ * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
+ * The file should return an array of data rows (column name => column value), each corresponding to a row in the table.
+ *
+ * If the data file does not exist, an empty array will be returned.
+ *
+ * @return array the data rows to be inserted into the database table.
+ */
+ protected function getData()
+ {
+ if ($this->dataFile === null) {
+ $class = new \ReflectionClass($this);
+ $dataFile = dirname($class->getFileName()) . '/data/' . $this->getTableSchema()->fullName . '.php';
- /**
- * Returns the fixture data.
- *
- * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
- * The file should return an array of data rows (column name => column value), each corresponding to a row in the table.
- *
- * If the data file does not exist, an empty array will be returned.
- *
- * @return array the data rows to be inserted into the database table.
- */
- protected function getData()
- {
- if ($this->dataFile === null) {
- $class = new \ReflectionClass($this);
- $dataFile = dirname($class->getFileName()) . '/data/' . $this->getTableSchema()->fullName . '.php';
- return is_file($dataFile) ? require($dataFile) : [];
- } else {
- return parent::getData();
- }
- }
+ return is_file($dataFile) ? require($dataFile) : [];
+ } else {
+ return parent::getData();
+ }
+ }
- /**
- * Removes all existing data from the specified table and resets sequence number to 1 (if any).
- * This method is called before populating fixture data into the table associated with this fixture.
- */
- protected function resetTable()
- {
- $table = $this->getTableSchema();
- $this->db->createCommand()->delete($table->fullName)->execute();
- if ($table->sequenceName !== null) {
- $this->db->createCommand()->resetSequence($table->fullName, 1)->execute();
- }
- }
+ /**
+ * Removes all existing data from the specified table and resets sequence number to 1 (if any).
+ * This method is called before populating fixture data into the table associated with this fixture.
+ */
+ protected function resetTable()
+ {
+ $table = $this->getTableSchema();
+ $this->db->createCommand()->delete($table->fullName)->execute();
+ if ($table->sequenceName !== null) {
+ $this->db->createCommand()->resetSequence($table->fullName, 1)->execute();
+ }
+ }
- /**
- * @return TableSchema the schema information of the database table associated with this fixture.
- * @throws \yii\base\InvalidConfigException if the table does not exist
- */
- public function getTableSchema()
- {
- if ($this->_table !== null) {
- return $this->_table;
- }
+ /**
+ * @return TableSchema the schema information of the database table associated with this fixture.
+ * @throws \yii\base\InvalidConfigException if the table does not exist
+ */
+ public function getTableSchema()
+ {
+ if ($this->_table !== null) {
+ return $this->_table;
+ }
- $db = $this->db;
- $tableName = $this->tableName;
- if ($tableName === null) {
- /** @var \yii\db\ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- $tableName = $modelClass::tableName();
- }
+ $db = $this->db;
+ $tableName = $this->tableName;
+ if ($tableName === null) {
+ /** @var \yii\db\ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+ $tableName = $modelClass::tableName();
+ }
- $this->_table = $db->getSchema()->getTableSchema($tableName);
- if ($this->_table === null) {
- throw new InvalidConfigException("Table does not exist: {$tableName}");
- }
+ $this->_table = $db->getSchema()->getTableSchema($tableName);
+ if ($this->_table === null) {
+ throw new InvalidConfigException("Table does not exist: {$tableName}");
+ }
- return $this->_table;
- }
+ return $this->_table;
+ }
}
diff --git a/framework/test/BaseActiveFixture.php b/framework/test/BaseActiveFixture.php
index 7300bdfbb43..f82fefb7e01 100644
--- a/framework/test/BaseActiveFixture.php
+++ b/framework/test/BaseActiveFixture.php
@@ -19,88 +19,88 @@
*/
abstract class BaseActiveFixture extends DbFixture implements \IteratorAggregate, \ArrayAccess, \Countable
{
- use ArrayAccessTrait;
+ use ArrayAccessTrait;
- /**
- * @var string the AR model class associated with this fixture.
- */
- public $modelClass;
- /**
- * @var array the data rows. Each array element represents one row of data (column name => column value).
- */
- public $data = [];
- /**
- * @var string|boolean the file path or path alias of the data file that contains the fixture data
- * to be returned by [[getData()]]. You can set this property to be false to prevent loading any data.
- */
- public $dataFile;
- /**
- * @var \yii\db\ActiveRecord[] the loaded AR models
- */
- private $_models = [];
+ /**
+ * @var string the AR model class associated with this fixture.
+ */
+ public $modelClass;
+ /**
+ * @var array the data rows. Each array element represents one row of data (column name => column value).
+ */
+ public $data = [];
+ /**
+ * @var string|boolean the file path or path alias of the data file that contains the fixture data
+ * to be returned by [[getData()]]. You can set this property to be false to prevent loading any data.
+ */
+ public $dataFile;
+ /**
+ * @var \yii\db\ActiveRecord[] the loaded AR models
+ */
+ private $_models = [];
+ /**
+ * Returns the AR model by the specified model name.
+ * A model name is the key of the corresponding data row in [[data]].
+ * @param string $name the model name.
+ * @return null|\yii\db\ActiveRecord the AR model, or null if the model cannot be found in the database
+ * @throws \yii\base\InvalidConfigException if [[modelClass]] is not set.
+ */
+ public function getModel($name)
+ {
+ if (!isset($this->data[$name])) {
+ return null;
+ }
+ if (array_key_exists($name, $this->_models)) {
+ return $this->_models[$name];
+ }
- /**
- * Returns the AR model by the specified model name.
- * A model name is the key of the corresponding data row in [[data]].
- * @param string $name the model name.
- * @return null|\yii\db\ActiveRecord the AR model, or null if the model cannot be found in the database
- * @throws \yii\base\InvalidConfigException if [[modelClass]] is not set.
- */
- public function getModel($name)
- {
- if (!isset($this->data[$name])) {
- return null;
- }
- if (array_key_exists($name, $this->_models)) {
- return $this->_models[$name];
- }
+ if ($this->modelClass === null) {
+ throw new InvalidConfigException('The "modelClass" property must be set.');
+ }
+ $row = $this->data[$name];
+ /** @var \yii\db\ActiveRecord $modelClass */
+ $modelClass = $this->modelClass;
+ /** @var \yii\db\ActiveRecord $model */
+ $model = new $modelClass;
+ $keys = [];
+ foreach ($model->primaryKey() as $key) {
+ $keys[$key] = isset($row[$key]) ? $row[$key] : null;
+ }
- if ($this->modelClass === null) {
- throw new InvalidConfigException('The "modelClass" property must be set.');
- }
- $row = $this->data[$name];
- /** @var \yii\db\ActiveRecord $modelClass */
- $modelClass = $this->modelClass;
- /** @var \yii\db\ActiveRecord $model */
- $model = new $modelClass;
- $keys = [];
- foreach ($model->primaryKey() as $key) {
- $keys[$key] = isset($row[$key]) ? $row[$key] : null;
- }
- return $this->_models[$name] = $modelClass::find($keys);
- }
+ return $this->_models[$name] = $modelClass::find($keys);
+ }
- /**
- * Loads the fixture.
- *
- * The default implementation simply stores the data returned by [[getData()]] in [[data]].
- * You should usually override this method by putting the data into the underlying database.
- */
- public function load()
- {
- $this->data = $this->getData();
- }
+ /**
+ * Loads the fixture.
+ *
+ * The default implementation simply stores the data returned by [[getData()]] in [[data]].
+ * You should usually override this method by putting the data into the underlying database.
+ */
+ public function load()
+ {
+ $this->data = $this->getData();
+ }
- /**
- * Returns the fixture data.
- *
- * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
- * The file should return the data array that will be stored in [[data]] after inserting into the database.
- *
- * @return array the data to be put into the database
- * @throws InvalidConfigException if the specified data file does not exist.
- */
- protected function getData()
- {
- if ($this->dataFile === false || $this->dataFile === null) {
- return [];
- }
- $dataFile = Yii::getAlias($this->dataFile);
- if (is_file($dataFile)) {
- return require($dataFile);
- } else {
- throw new InvalidConfigException("Fixture data file does not exist: {$this->dataFile}");
- }
- }
+ /**
+ * Returns the fixture data.
+ *
+ * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
+ * The file should return the data array that will be stored in [[data]] after inserting into the database.
+ *
+ * @return array the data to be put into the database
+ * @throws InvalidConfigException if the specified data file does not exist.
+ */
+ protected function getData()
+ {
+ if ($this->dataFile === false || $this->dataFile === null) {
+ return [];
+ }
+ $dataFile = Yii::getAlias($this->dataFile);
+ if (is_file($dataFile)) {
+ return require($dataFile);
+ } else {
+ throw new InvalidConfigException("Fixture data file does not exist: {$this->dataFile}");
+ }
+ }
}
diff --git a/framework/test/DbFixture.php b/framework/test/DbFixture.php
index 2b5e6cc4928..2b165490597 100644
--- a/framework/test/DbFixture.php
+++ b/framework/test/DbFixture.php
@@ -21,25 +21,24 @@
*/
abstract class DbFixture extends Fixture
{
- /**
- * @var Connection|string the DB connection object or the application component ID of the DB connection.
- * After the DbFixture object is created, if you want to change this property, you should only assign it
- * with a DB connection object.
- */
- public $db = 'db';
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ * After the DbFixture object is created, if you want to change this property, you should only assign it
+ * with a DB connection object.
+ */
+ public $db = 'db';
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!is_object($this->db)) {
- throw new InvalidConfigException("The 'db' property must be either a DB connection instance or the application component ID of a DB connection.");
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!is_object($this->db)) {
+ throw new InvalidConfigException("The 'db' property must be either a DB connection instance or the application component ID of a DB connection.");
+ }
+ }
}
diff --git a/framework/test/Fixture.php b/framework/test/Fixture.php
index e22a139e68f..e7a41574d92 100644
--- a/framework/test/Fixture.php
+++ b/framework/test/Fixture.php
@@ -29,56 +29,55 @@
*/
class Fixture extends Component
{
- /**
- * @var array the fixtures that this fixture depends on. This must be a list of the dependent
- * fixture class names.
- */
- public $depends = [];
+ /**
+ * @var array the fixtures that this fixture depends on. This must be a list of the dependent
+ * fixture class names.
+ */
+ public $depends = [];
+ /**
+ * Loads the fixture.
+ * This method is called before performing every test method.
+ * You should override this method with concrete implementation about how to set up the fixture.
+ */
+ public function load()
+ {
+ }
- /**
- * Loads the fixture.
- * This method is called before performing every test method.
- * You should override this method with concrete implementation about how to set up the fixture.
- */
- public function load()
- {
- }
+ /**
+ * This method is called BEFORE any fixture data is loaded for the current test.
+ */
+ public function beforeLoad()
+ {
+ }
- /**
- * This method is called BEFORE any fixture data is loaded for the current test.
- */
- public function beforeLoad()
- {
- }
+ /**
+ * This method is called AFTER all fixture data have been loaded for the current test.
+ */
+ public function afterLoad()
+ {
+ }
- /**
- * This method is called AFTER all fixture data have been loaded for the current test.
- */
- public function afterLoad()
- {
- }
+ /**
+ * Unloads the fixture.
+ * This method is called after every test method finishes.
+ * You may override this method to perform necessary cleanup work for the fixture.
+ */
+ public function unload()
+ {
+ }
- /**
- * Unloads the fixture.
- * This method is called after every test method finishes.
- * You may override this method to perform necessary cleanup work for the fixture.
- */
- public function unload()
- {
- }
+ /**
+ * This method is called BEFORE any fixture data is unloaded for the current test.
+ */
+ public function beforeUnload()
+ {
+ }
- /**
- * This method is called BEFORE any fixture data is unloaded for the current test.
- */
- public function beforeUnload()
- {
- }
-
- /**
- * This method is called AFTER all fixture data have been unloaded for the current test.
- */
- public function afterUnload()
- {
- }
+ /**
+ * This method is called AFTER all fixture data have been unloaded for the current test.
+ */
+ public function afterUnload()
+ {
+ }
}
diff --git a/framework/test/FixtureTrait.php b/framework/test/FixtureTrait.php
index f82c56d86f9..5a87e22195e 100644
--- a/framework/test/FixtureTrait.php
+++ b/framework/test/FixtureTrait.php
@@ -24,182 +24,184 @@
*/
trait FixtureTrait
{
- /**
- * @var array the list of fixture objects available for the current test.
- * The array keys are the corresponding fixture class names.
- * The fixtures are listed in their dependency order. That is, fixture A is listed before B
- * if B depends on A.
- */
- private $_fixtures;
-
- /**
- * Declares the fixtures that are needed by the current test case.
- * The return value of this method must be an array of fixture configurations. For example,
- *
- * ```php
- * [
- * // anonymous fixture
- * PostFixture::className(),
- * // "users" fixture
- * 'users' => UserFixture::className(),
- * // "cache" fixture with configuration
- * 'cache' => [
- * 'class' => CacheFixture::className(),
- * 'host' => 'xxx',
- * ],
- * ]
- * ```
- *
- * Note that the actual fixtures used for a test case will include both [[globalFixtures()]]
- * and [[fixtures()]].
- *
- * @return array the fixtures needed by the current test case
- */
- public function fixtures()
- {
- return [];
- }
-
- /**
- * Declares the fixtures shared required by different test cases.
- * The return value should be similar to that of [[fixtures()]].
- * You should usually override this method in a base class.
- * @return array the fixtures shared and required by different test cases.
- * @see fixtures()
- */
- public function globalFixtures()
- {
- return [];
- }
-
- /**
- * Loads the specified fixtures.
- * This method will call [[Fixture::load()]] for every fixture object.
- * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
- * the return value of [[getFixtures()]] will be used.
- */
- public function loadFixtures($fixtures = null)
- {
- if ($fixtures === null) {
- $fixtures = $this->getFixtures();
- }
-
- /** @var Fixture $fixture */
- foreach ($fixtures as $fixture) {
- $fixture->beforeLoad();
- }
- foreach ($fixtures as $fixture) {
- $fixture->load();
- }
- foreach (array_reverse($fixtures) as $fixture) {
- $fixture->afterLoad();
- }
- }
-
- /**
- * Unloads the specified fixtures.
- * This method will call [[Fixture::unload()]] for every fixture object.
- * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
- * the return value of [[getFixtures()]] will be used.
- */
- public function unloadFixtures($fixtures = null)
- {
- if ($fixtures === null) {
- $fixtures = $this->getFixtures();
- }
-
- /** @var Fixture $fixture */
- foreach ($fixtures as $fixture) {
- $fixture->beforeUnload();
- }
- $fixtures = array_reverse($fixtures);
- foreach ($fixtures as $fixture) {
- $fixture->unload();
- }
- foreach ($fixtures as $fixture) {
- $fixture->afterUnload();
- }
- }
-
- /**
- * Returns the fixture objects as specified in [[globalFixtures()]] and [[fixtures()]].
- * @return Fixture[] the loaded fixtures for the current test case
- */
- public function getFixtures()
- {
- if ($this->_fixtures === null) {
- $this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
- }
- return $this->_fixtures;
- }
-
- /**
- * Returns the named fixture.
- * @param string $name the fixture name. This can be either the fixture alias name, or the class name if the alias is not used.
- * @return Fixture the fixture object, or null if the named fixture does not exist.
- */
- public function getFixture($name)
- {
- if ($this->_fixtures === null) {
- $this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
- }
- $name = ltrim($name, '\\');
- return isset($this->_fixtures[$name]) ? $this->_fixtures[$name] : null;
- }
-
- /**
- * Creates the specified fixture instances.
- * All dependent fixtures will also be created.
- * @param array $fixtures the fixtures to be created. You may provide fixture names or fixture configurations.
- * If this parameter is not provided, the fixtures specified in [[globalFixtures()]] and [[fixtures()]] will be created.
- * @return Fixture[] the created fixture instances
- * @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among
- * the fixtures is detected.
- */
- protected function createFixtures(array $fixtures)
- {
- // normalize fixture configurations
- $config = []; // configuration provided in test case
- $aliases = []; // class name => alias or class name
- foreach ($fixtures as $name => $fixture) {
- if (!is_array($fixture)) {
- $class = ltrim($fixture, '\\');
- $fixtures[$name] = ['class' => $class];
- $aliases[$class] = is_integer($name) ? $class : $name;
- } elseif (isset($fixture['class'])) {
- $class = ltrim($fixture['class'], '\\');
- $config[$class] = $fixture;
- $aliases[$class] = $name;
- } else {
- throw new InvalidConfigException("You must specify 'class' for the fixture '$name'.");
- }
- }
-
- // create fixture instances
- $instances = [];
- $stack = array_reverse($fixtures);
- while (($fixture = array_pop($stack)) !== null) {
- if ($fixture instanceof Fixture) {
- $class = get_class($fixture);
- $name = isset($aliases[$class]) ? $aliases[$class] : $class;
- unset($instances[$name]); // unset so that the fixture is added to the last in the next line
- $instances[$name] = $fixture;
- } else {
- $class = ltrim($fixture['class'], '\\');
- $name = isset($aliases[$class]) ? $aliases[$class] : $class;
- if (!isset($instances[$name])) {
- $instances[$name] = false;
- $stack[] = $fixture = Yii::createObject($fixture);
- foreach ($fixture->depends as $dep) {
- // need to use the configuration provided in test case
- $stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep];
- }
- } elseif ($instances[$name] === false) {
- throw new InvalidConfigException("A circular dependency is detected for fixture '$class'.");
- }
- }
- }
-
- return $instances;
- }
+ /**
+ * @var array the list of fixture objects available for the current test.
+ * The array keys are the corresponding fixture class names.
+ * The fixtures are listed in their dependency order. That is, fixture A is listed before B
+ * if B depends on A.
+ */
+ private $_fixtures;
+
+ /**
+ * Declares the fixtures that are needed by the current test case.
+ * The return value of this method must be an array of fixture configurations. For example,
+ *
+ * ```php
+ * [
+ * // anonymous fixture
+ * PostFixture::className(),
+ * // "users" fixture
+ * 'users' => UserFixture::className(),
+ * // "cache" fixture with configuration
+ * 'cache' => [
+ * 'class' => CacheFixture::className(),
+ * 'host' => 'xxx',
+ * ],
+ * ]
+ * ```
+ *
+ * Note that the actual fixtures used for a test case will include both [[globalFixtures()]]
+ * and [[fixtures()]].
+ *
+ * @return array the fixtures needed by the current test case
+ */
+ public function fixtures()
+ {
+ return [];
+ }
+
+ /**
+ * Declares the fixtures shared required by different test cases.
+ * The return value should be similar to that of [[fixtures()]].
+ * You should usually override this method in a base class.
+ * @return array the fixtures shared and required by different test cases.
+ * @see fixtures()
+ */
+ public function globalFixtures()
+ {
+ return [];
+ }
+
+ /**
+ * Loads the specified fixtures.
+ * This method will call [[Fixture::load()]] for every fixture object.
+ * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
+ * the return value of [[getFixtures()]] will be used.
+ */
+ public function loadFixtures($fixtures = null)
+ {
+ if ($fixtures === null) {
+ $fixtures = $this->getFixtures();
+ }
+
+ /** @var Fixture $fixture */
+ foreach ($fixtures as $fixture) {
+ $fixture->beforeLoad();
+ }
+ foreach ($fixtures as $fixture) {
+ $fixture->load();
+ }
+ foreach (array_reverse($fixtures) as $fixture) {
+ $fixture->afterLoad();
+ }
+ }
+
+ /**
+ * Unloads the specified fixtures.
+ * This method will call [[Fixture::unload()]] for every fixture object.
+ * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
+ * the return value of [[getFixtures()]] will be used.
+ */
+ public function unloadFixtures($fixtures = null)
+ {
+ if ($fixtures === null) {
+ $fixtures = $this->getFixtures();
+ }
+
+ /** @var Fixture $fixture */
+ foreach ($fixtures as $fixture) {
+ $fixture->beforeUnload();
+ }
+ $fixtures = array_reverse($fixtures);
+ foreach ($fixtures as $fixture) {
+ $fixture->unload();
+ }
+ foreach ($fixtures as $fixture) {
+ $fixture->afterUnload();
+ }
+ }
+
+ /**
+ * Returns the fixture objects as specified in [[globalFixtures()]] and [[fixtures()]].
+ * @return Fixture[] the loaded fixtures for the current test case
+ */
+ public function getFixtures()
+ {
+ if ($this->_fixtures === null) {
+ $this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
+ }
+
+ return $this->_fixtures;
+ }
+
+ /**
+ * Returns the named fixture.
+ * @param string $name the fixture name. This can be either the fixture alias name, or the class name if the alias is not used.
+ * @return Fixture the fixture object, or null if the named fixture does not exist.
+ */
+ public function getFixture($name)
+ {
+ if ($this->_fixtures === null) {
+ $this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
+ }
+ $name = ltrim($name, '\\');
+
+ return isset($this->_fixtures[$name]) ? $this->_fixtures[$name] : null;
+ }
+
+ /**
+ * Creates the specified fixture instances.
+ * All dependent fixtures will also be created.
+ * @param array $fixtures the fixtures to be created. You may provide fixture names or fixture configurations.
+ * If this parameter is not provided, the fixtures specified in [[globalFixtures()]] and [[fixtures()]] will be created.
+ * @return Fixture[] the created fixture instances
+ * @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among
+ * the fixtures is detected.
+ */
+ protected function createFixtures(array $fixtures)
+ {
+ // normalize fixture configurations
+ $config = []; // configuration provided in test case
+ $aliases = []; // class name => alias or class name
+ foreach ($fixtures as $name => $fixture) {
+ if (!is_array($fixture)) {
+ $class = ltrim($fixture, '\\');
+ $fixtures[$name] = ['class' => $class];
+ $aliases[$class] = is_integer($name) ? $class : $name;
+ } elseif (isset($fixture['class'])) {
+ $class = ltrim($fixture['class'], '\\');
+ $config[$class] = $fixture;
+ $aliases[$class] = $name;
+ } else {
+ throw new InvalidConfigException("You must specify 'class' for the fixture '$name'.");
+ }
+ }
+
+ // create fixture instances
+ $instances = [];
+ $stack = array_reverse($fixtures);
+ while (($fixture = array_pop($stack)) !== null) {
+ if ($fixture instanceof Fixture) {
+ $class = get_class($fixture);
+ $name = isset($aliases[$class]) ? $aliases[$class] : $class;
+ unset($instances[$name]); // unset so that the fixture is added to the last in the next line
+ $instances[$name] = $fixture;
+ } else {
+ $class = ltrim($fixture['class'], '\\');
+ $name = isset($aliases[$class]) ? $aliases[$class] : $class;
+ if (!isset($instances[$name])) {
+ $instances[$name] = false;
+ $stack[] = $fixture = Yii::createObject($fixture);
+ foreach ($fixture->depends as $dep) {
+ // need to use the configuration provided in test case
+ $stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep];
+ }
+ } elseif ($instances[$name] === false) {
+ throw new InvalidConfigException("A circular dependency is detected for fixture '$class'.");
+ }
+ }
+ }
+
+ return $instances;
+ }
}
diff --git a/framework/test/InitDbFixture.php b/framework/test/InitDbFixture.php
index b54e52fd098..b399979b8ca 100644
--- a/framework/test/InitDbFixture.php
+++ b/framework/test/InitDbFixture.php
@@ -26,72 +26,71 @@
*/
class InitDbFixture extends DbFixture
{
- /**
- * @var string the init script file that should be executed when loading this fixture.
- * This should be either a file path or path alias. Note that if the file does not exist,
- * no error will be raised.
- */
- public $initScript = '@app/tests/fixtures/initdb.php';
- /**
- * @var array list of database schemas that the test tables may reside in. Defaults to
- * `['']`, meaning using the default schema (an empty string refers to the
- * default schema). This property is mainly used when turning on and off integrity checks
- * so that fixture data can be populated into the database without causing problem.
- */
- public $schemas = [''];
+ /**
+ * @var string the init script file that should be executed when loading this fixture.
+ * This should be either a file path or path alias. Note that if the file does not exist,
+ * no error will be raised.
+ */
+ public $initScript = '@app/tests/fixtures/initdb.php';
+ /**
+ * @var array list of database schemas that the test tables may reside in. Defaults to
+ * `['']`, meaning using the default schema (an empty string refers to the
+ * default schema). This property is mainly used when turning on and off integrity checks
+ * so that fixture data can be populated into the database without causing problem.
+ */
+ public $schemas = [''];
+ /**
+ * @inheritdoc
+ */
+ public function beforeLoad()
+ {
+ $this->checkIntegrity(false);
+ }
- /**
- * @inheritdoc
- */
- public function beforeLoad()
- {
- $this->checkIntegrity(false);
- }
+ /**
+ * @inheritdoc
+ */
+ public function afterLoad()
+ {
+ $this->checkIntegrity(true);
+ }
- /**
- * @inheritdoc
- */
- public function afterLoad()
- {
- $this->checkIntegrity(true);
- }
+ /**
+ * @inheritdoc
+ */
+ public function load()
+ {
+ $file = Yii::getAlias($this->initScript);
+ if (is_file($file)) {
+ require($file);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function load()
- {
- $file = Yii::getAlias($this->initScript);
- if (is_file($file)) {
- require($file);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function beforeUnload()
+ {
+ $this->checkIntegrity(false);
+ }
- /**
- * @inheritdoc
- */
- public function beforeUnload()
- {
- $this->checkIntegrity(false);
- }
+ /**
+ * @inheritdoc
+ */
+ public function afterUnload()
+ {
+ $this->checkIntegrity(true);
+ }
- /**
- * @inheritdoc
- */
- public function afterUnload()
- {
- $this->checkIntegrity(true);
- }
-
- /**
- * Toggles the DB integrity check.
- * @param boolean $check whether to turn on or off the integrity check.
- */
- public function checkIntegrity($check)
- {
- foreach ($this->schemas as $schema) {
- $this->db->createCommand()->checkIntegrity($check, $schema)->execute();
- }
- }
+ /**
+ * Toggles the DB integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ */
+ public function checkIntegrity($check)
+ {
+ foreach ($this->schemas as $schema) {
+ $this->db->createCommand()->checkIntegrity($check, $schema)->execute();
+ }
+ }
}
diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php
index 8f66dc3a9a6..d8859098535 100644
--- a/framework/validators/BooleanValidator.php
+++ b/framework/validators/BooleanValidator.php
@@ -20,71 +20,72 @@
*/
class BooleanValidator extends Validator
{
- /**
- * @var mixed the value representing true status. Defaults to '1'.
- */
- public $trueValue = '1';
- /**
- * @var mixed the value representing false status. Defaults to '0'.
- */
- public $falseValue = '0';
- /**
- * @var boolean whether the comparison to [[trueValue]] and [[falseValue]] is strict.
- * When this is true, the attribute value and type must both match those of [[trueValue]] or [[falseValue]].
- * Defaults to false, meaning only the value needs to be matched.
- */
- public $strict = false;
+ /**
+ * @var mixed the value representing true status. Defaults to '1'.
+ */
+ public $trueValue = '1';
+ /**
+ * @var mixed the value representing false status. Defaults to '0'.
+ */
+ public $falseValue = '0';
+ /**
+ * @var boolean whether the comparison to [[trueValue]] and [[falseValue]] is strict.
+ * When this is true, the attribute value and type must both match those of [[trueValue]] or [[falseValue]].
+ * Defaults to false, meaning only the value needs to be matched.
+ */
+ public $strict = false;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} must be either "{true}" or "{false}".');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} must be either "{true}" or "{false}".');
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- $valid = !$this->strict && ($value == $this->trueValue || $value == $this->falseValue)
- || $this->strict && ($value === $this->trueValue || $value === $this->falseValue);
- if (!$valid) {
- return [$this->message, [
- 'true' => $this->trueValue,
- 'false' => $this->falseValue,
- ]];
- } else {
- return null;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ $valid = !$this->strict && ($value == $this->trueValue || $value == $this->falseValue)
+ || $this->strict && ($value === $this->trueValue || $value === $this->falseValue);
+ if (!$valid) {
+ return [$this->message, [
+ 'true' => $this->trueValue,
+ 'false' => $this->falseValue,
+ ]];
+ } else {
+ return null;
+ }
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $options = [
- 'trueValue' => $this->trueValue,
- 'falseValue' => $this->falseValue,
- 'message' => Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $object->getAttributeLabel($attribute),
- 'true' => $this->trueValue,
- 'false' => $this->falseValue,
- ], Yii::$app->language),
- ];
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
- if ($this->strict) {
- $options['strict'] = 1;
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $options = [
+ 'trueValue' => $this->trueValue,
+ 'falseValue' => $this->falseValue,
+ 'message' => Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ 'true' => $this->trueValue,
+ 'false' => $this->falseValue,
+ ], Yii::$app->language),
+ ];
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+ if ($this->strict) {
+ $options['strict'] = 1;
+ }
- ValidationAsset::register($view);
- return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');';
- }
+ ValidationAsset::register($view);
+
+ return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');';
+ }
}
diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php
index d1e6337466e..128da1e32c4 100644
--- a/framework/validators/CompareValidator.php
+++ b/framework/validators/CompareValidator.php
@@ -29,177 +29,178 @@
*/
class CompareValidator extends Validator
{
- /**
- * @var string the name of the attribute to be compared with. When both this property
- * and [[compareValue]] are set, the latter takes precedence. If neither is set,
- * it assumes the comparison is against another attribute whose name is formed by
- * appending '_repeat' to the attribute being validated. For example, if 'password' is
- * being validated, then the attribute to be compared would be 'password_repeat'.
- * @see compareValue
- */
- public $compareAttribute;
- /**
- * @var mixed the constant value to be compared with. When both this property
- * and [[compareAttribute]] are set, this property takes precedence.
- * @see compareAttribute
- */
- public $compareValue;
- /**
- * @var string the operator for comparison. The following operators are supported:
- *
- * - '==': validates to see if the two values are equal. The comparison is done is non-strict mode.
- * - '===': validates to see if the two values are equal. The comparison is done is strict mode.
- * - '!=': validates to see if the two values are NOT equal. The comparison is done is non-strict mode.
- * - '!==': validates to see if the two values are NOT equal. The comparison is done is strict mode.
- * - `>`: validates to see if the value being validated is greater than the value being compared with.
- * - `>=`: validates to see if the value being validated is greater than or equal to the value being compared with.
- * - `<`: validates to see if the value being validated is less than the value being compared with.
- * - `<=`: validates to see if the value being validated is less than or equal to the value being compared with.
- */
- public $operator = '==';
- /**
- * @var string the user-defined error message. It may contain the following placeholders which
- * will be replaced accordingly by the validator:
- *
- * - `{attribute}`: the label of the attribute being validated
- * - `{value}`: the value of the attribute being validated
- * - `{compareValue}`: the value or the attribute label to be compared with
- * - `{compareAttribute}`: the label of the attribute to be compared with
- */
- public $message;
+ /**
+ * @var string the name of the attribute to be compared with. When both this property
+ * and [[compareValue]] are set, the latter takes precedence. If neither is set,
+ * it assumes the comparison is against another attribute whose name is formed by
+ * appending '_repeat' to the attribute being validated. For example, if 'password' is
+ * being validated, then the attribute to be compared would be 'password_repeat'.
+ * @see compareValue
+ */
+ public $compareAttribute;
+ /**
+ * @var mixed the constant value to be compared with. When both this property
+ * and [[compareAttribute]] are set, this property takes precedence.
+ * @see compareAttribute
+ */
+ public $compareValue;
+ /**
+ * @var string the operator for comparison. The following operators are supported:
+ *
+ * - '==': validates to see if the two values are equal. The comparison is done is non-strict mode.
+ * - '===': validates to see if the two values are equal. The comparison is done is strict mode.
+ * - '!=': validates to see if the two values are NOT equal. The comparison is done is non-strict mode.
+ * - '!==': validates to see if the two values are NOT equal. The comparison is done is strict mode.
+ * - `>`: validates to see if the value being validated is greater than the value being compared with.
+ * - `>=`: validates to see if the value being validated is greater than or equal to the value being compared with.
+ * - `<`: validates to see if the value being validated is less than the value being compared with.
+ * - `<=`: validates to see if the value being validated is less than or equal to the value being compared with.
+ */
+ public $operator = '==';
+ /**
+ * @var string the user-defined error message. It may contain the following placeholders which
+ * will be replaced accordingly by the validator:
+ *
+ * - `{attribute}`: the label of the attribute being validated
+ * - `{value}`: the value of the attribute being validated
+ * - `{compareValue}`: the value or the attribute label to be compared with
+ * - `{compareAttribute}`: the label of the attribute to be compared with
+ */
+ public $message;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ switch ($this->operator) {
+ case '==':
+ $this->message = Yii::t('yii', '{attribute} must be repeated exactly.');
+ break;
+ case '===':
+ $this->message = Yii::t('yii', '{attribute} must be repeated exactly.');
+ break;
+ case '!=':
+ $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".');
+ break;
+ case '!==':
+ $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".');
+ break;
+ case '>':
+ $this->message = Yii::t('yii', '{attribute} must be greater than "{compareValue}".');
+ break;
+ case '>=':
+ $this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValue}".');
+ break;
+ case '<':
+ $this->message = Yii::t('yii', '{attribute} must be less than "{compareValue}".');
+ break;
+ case '<=':
+ $this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValue}".');
+ break;
+ default:
+ throw new InvalidConfigException("Unknown operator: {$this->operator}");
+ }
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- switch ($this->operator) {
- case '==':
- $this->message = Yii::t('yii', '{attribute} must be repeated exactly.');
- break;
- case '===':
- $this->message = Yii::t('yii', '{attribute} must be repeated exactly.');
- break;
- case '!=':
- $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".');
- break;
- case '!==':
- $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".');
- break;
- case '>':
- $this->message = Yii::t('yii', '{attribute} must be greater than "{compareValue}".');
- break;
- case '>=':
- $this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValue}".');
- break;
- case '<':
- $this->message = Yii::t('yii', '{attribute} must be less than "{compareValue}".');
- break;
- case '<=':
- $this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValue}".');
- break;
- default:
- throw new InvalidConfigException("Unknown operator: {$this->operator}");
- }
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $value = $object->$attribute;
+ if (is_array($value)) {
+ $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $value = $object->$attribute;
- if (is_array($value)) {
- $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- return;
- }
- if ($this->compareValue !== null) {
- $compareLabel = $compareValue = $this->compareValue;
- } else {
- $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
- $compareValue = $object->$compareAttribute;
- $compareLabel = $object->getAttributeLabel($compareAttribute);
- }
+ return;
+ }
+ if ($this->compareValue !== null) {
+ $compareLabel = $compareValue = $this->compareValue;
+ } else {
+ $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
+ $compareValue = $object->$compareAttribute;
+ $compareLabel = $object->getAttributeLabel($compareAttribute);
+ }
- if (!$this->compareValues($this->operator, $value, $compareValue)) {
- $this->addError($object, $attribute, $this->message, [
- 'compareAttribute' => $compareLabel,
- 'compareValue' => $compareValue,
- ]);
- }
- }
+ if (!$this->compareValues($this->operator, $value, $compareValue)) {
+ $this->addError($object, $attribute, $this->message, [
+ 'compareAttribute' => $compareLabel,
+ 'compareValue' => $compareValue,
+ ]);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- if ($this->compareValue === null) {
- throw new InvalidConfigException('CompareValidator::compareValue must be set.');
- }
- if (!$this->compareValues($this->operator, $value, $this->compareValue)) {
- return [$this->message, [
- 'compareAttribute' => $this->compareValue,
- 'compareValue' => $this->compareValue,
- ]];
- } else {
- return null;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ if ($this->compareValue === null) {
+ throw new InvalidConfigException('CompareValidator::compareValue must be set.');
+ }
+ if (!$this->compareValues($this->operator, $value, $this->compareValue)) {
+ return [$this->message, [
+ 'compareAttribute' => $this->compareValue,
+ 'compareValue' => $this->compareValue,
+ ]];
+ } else {
+ return null;
+ }
+ }
- /**
- * Compares two values with the specified operator.
- * @param string $operator the comparison operator
- * @param mixed $value the value being compared
- * @param mixed $compareValue another value being compared
- * @return boolean whether the comparison using the specified operator is true.
- */
- protected function compareValues($operator, $value, $compareValue)
- {
- switch ($operator) {
- case '==': return $value == $compareValue;
- case '===': return $value === $compareValue;
- case '!=': return $value != $compareValue;
- case '!==': return $value !== $compareValue;
- case '>': return $value > $compareValue;
- case '>=': return $value >= $compareValue;
- case '<': return $value < $compareValue;
- case '<=': return $value <= $compareValue;
- default: return false;
- }
- }
+ /**
+ * Compares two values with the specified operator.
+ * @param string $operator the comparison operator
+ * @param mixed $value the value being compared
+ * @param mixed $compareValue another value being compared
+ * @return boolean whether the comparison using the specified operator is true.
+ */
+ protected function compareValues($operator, $value, $compareValue)
+ {
+ switch ($operator) {
+ case '==': return $value == $compareValue;
+ case '===': return $value === $compareValue;
+ case '!=': return $value != $compareValue;
+ case '!==': return $value !== $compareValue;
+ case '>': return $value > $compareValue;
+ case '>=': return $value >= $compareValue;
+ case '<': return $value < $compareValue;
+ case '<=': return $value <= $compareValue;
+ default: return false;
+ }
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $options = ['operator' => $this->operator];
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $options = ['operator' => $this->operator];
- if ($this->compareValue !== null) {
- $options['compareValue'] = $this->compareValue;
- $compareValue = $this->compareValue;
- } else {
- $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
- $compareValue = $object->getAttributeLabel($compareAttribute);
- $options['compareAttribute'] = Html::getInputId($object, $compareAttribute);
- }
+ if ($this->compareValue !== null) {
+ $options['compareValue'] = $this->compareValue;
+ $compareValue = $this->compareValue;
+ } else {
+ $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
+ $compareValue = $object->getAttributeLabel($compareAttribute);
+ $options['compareAttribute'] = Html::getInputId($object, $compareAttribute);
+ }
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
- $options['message'] = Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $object->getAttributeLabel($attribute),
- 'compareAttribute' => $compareValue,
- 'compareValue' => $compareValue,
- ], Yii::$app->language);
+ $options['message'] = Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ 'compareAttribute' => $compareValue,
+ 'compareValue' => $compareValue,
+ ], Yii::$app->language);
- ValidationAsset::register($view);
- return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');';
- }
+ ValidationAsset::register($view);
+
+ return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');';
+ }
}
diff --git a/framework/validators/DateValidator.php b/framework/validators/DateValidator.php
index d79aa857c81..7819f802378 100644
--- a/framework/validators/DateValidator.php
+++ b/framework/validators/DateValidator.php
@@ -18,56 +18,57 @@
*/
class DateValidator extends Validator
{
- /**
- * @var string the date format that the value being validated should follow.
- * Please refer to on
- * supported formats.
- */
- public $format = 'Y-m-d';
- /**
- * @var string the name of the attribute to receive the parsing result.
- * When this property is not null and the validation is successful, the named attribute will
- * receive the parsing result.
- */
- public $timestampAttribute;
+ /**
+ * @var string the date format that the value being validated should follow.
+ * Please refer to on
+ * supported formats.
+ */
+ public $format = 'Y-m-d';
+ /**
+ * @var string the name of the attribute to receive the parsing result.
+ * When this property is not null and the validation is successful, the named attribute will
+ * receive the parsing result.
+ */
+ public $timestampAttribute;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = Yii::t('yii', 'The format of {attribute} is invalid.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', 'The format of {attribute} is invalid.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $value = $object->$attribute;
- $result = $this->validateValue($value);
- if (!empty($result)) {
- $this->addError($object, $attribute, $result[0], $result[1]);
- } elseif ($this->timestampAttribute !== null) {
- $date = DateTime::createFromFormat($this->format, $value);
- $object->{$this->timestampAttribute} = $date->getTimestamp();
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $value = $object->$attribute;
+ $result = $this->validateValue($value);
+ if (!empty($result)) {
+ $this->addError($object, $attribute, $result[0], $result[1]);
+ } elseif ($this->timestampAttribute !== null) {
+ $date = DateTime::createFromFormat($this->format, $value);
+ $object->{$this->timestampAttribute} = $date->getTimestamp();
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- if (is_array($value)) {
- return [$this->message, []];
- }
- $date = DateTime::createFromFormat($this->format, $value);
- $errors = DateTime::getLastErrors();
- $invalid = $date === false || $errors['error_count'] || $errors['warning_count'];
- return $invalid ? [$this->message, []] : null;
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ if (is_array($value)) {
+ return [$this->message, []];
+ }
+ $date = DateTime::createFromFormat($this->format, $value);
+ $errors = DateTime::getLastErrors();
+ $invalid = $date === false || $errors['error_count'] || $errors['warning_count'];
+
+ return $invalid ? [$this->message, []] : null;
+ }
}
diff --git a/framework/validators/DefaultValueValidator.php b/framework/validators/DefaultValueValidator.php
index 0db3a670034..e4ad59b7e57 100644
--- a/framework/validators/DefaultValueValidator.php
+++ b/framework/validators/DefaultValueValidator.php
@@ -18,23 +18,23 @@
*/
class DefaultValueValidator extends Validator
{
- /**
- * @var mixed the default value to be set to the specified attributes.
- */
- public $value;
- /**
- * @var boolean this property is overwritten to be false so that this validator will
- * be applied when the value being validated is empty.
- */
- public $skipOnEmpty = false;
+ /**
+ * @var mixed the default value to be set to the specified attributes.
+ */
+ public $value;
+ /**
+ * @var boolean this property is overwritten to be false so that this validator will
+ * be applied when the value being validated is empty.
+ */
+ public $skipOnEmpty = false;
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- if ($this->isEmpty($object->$attribute)) {
- $object->$attribute = $this->value;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ if ($this->isEmpty($object->$attribute)) {
+ $object->$attribute = $this->value;
+ }
+ }
}
diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php
index aa63a57eb7b..2056fd8eea3 100644
--- a/framework/validators/EmailValidator.php
+++ b/framework/validators/EmailValidator.php
@@ -20,96 +20,97 @@
*/
class EmailValidator extends Validator
{
- /**
- * @var string the regular expression used to validate the attribute value.
- * @see http://www.regular-expressions.info/email.html
- */
- public $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
- /**
- * @var string the regular expression used to validate email addresses with the name part.
- * This property is used only when [[allowName]] is true.
- * @see allowName
- */
- public $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
- /**
- * @var boolean whether to allow name in the email address (e.g. "John Smith "). Defaults to false.
- * @see fullPattern
- */
- public $allowName = false;
- /**
- * @var boolean whether to check whether the emails domain exists and has either an A or MX record.
- * Be aware of the fact that this check can fail due to temporary DNS problems even if the email address is
- * valid and an email would be deliverable. Defaults to false.
- */
- public $checkDNS = false;
- /**
- * @var boolean whether validation process should take into account IDN (internationalized domain
- * names). Defaults to false meaning that validation of emails containing IDN will always fail.
- * Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
- * otherwise an exception would be thrown.
- */
- public $enableIDN = false;
+ /**
+ * @var string the regular expression used to validate the attribute value.
+ * @see http://www.regular-expressions.info/email.html
+ */
+ public $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
+ /**
+ * @var string the regular expression used to validate email addresses with the name part.
+ * This property is used only when [[allowName]] is true.
+ * @see allowName
+ */
+ public $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
+ /**
+ * @var boolean whether to allow name in the email address (e.g. "John Smith "). Defaults to false.
+ * @see fullPattern
+ */
+ public $allowName = false;
+ /**
+ * @var boolean whether to check whether the emails domain exists and has either an A or MX record.
+ * Be aware of the fact that this check can fail due to temporary DNS problems even if the email address is
+ * valid and an email would be deliverable. Defaults to false.
+ */
+ public $checkDNS = false;
+ /**
+ * @var boolean whether validation process should take into account IDN (internationalized domain
+ * names). Defaults to false meaning that validation of emails containing IDN will always fail.
+ * Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
+ * otherwise an exception would be thrown.
+ */
+ public $enableIDN = false;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->enableIDN && !function_exists('idn_to_ascii')) {
+ throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
+ }
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} is not a valid email address.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->enableIDN && !function_exists('idn_to_ascii')) {
- throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
- }
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} is not a valid email address.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ // make sure string length is limited to avoid DOS attacks
+ if (!is_string($value) || strlen($value) >= 320) {
+ $valid = false;
+ } elseif (!preg_match('/^(.*)(.*)@(.*)(>?)$/', $value, $matches)) {
+ $valid = false;
+ } else {
+ $domain = $matches[3];
+ if ($this->enableIDN) {
+ $value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4];
+ }
+ $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
+ if ($valid && $this->checkDNS) {
+ $valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A');
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- // make sure string length is limited to avoid DOS attacks
- if (!is_string($value) || strlen($value) >= 320) {
- $valid = false;
- } elseif (!preg_match('/^(.*)(.*)@(.*)(>?)$/', $value, $matches)) {
- $valid = false;
- } else {
- $domain = $matches[3];
- if ($this->enableIDN) {
- $value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4];
- }
- $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
- if ($valid && $this->checkDNS) {
- $valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A');
- }
- }
- return $valid ? null : [$this->message, []];
- }
+ return $valid ? null : [$this->message, []];
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $options = [
- 'pattern' => new JsExpression($this->pattern),
- 'fullPattern' => new JsExpression($this->fullPattern),
- 'allowName' => $this->allowName,
- 'message' => Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $object->getAttributeLabel($attribute),
- ], Yii::$app->language),
- 'enableIDN' => (boolean)$this->enableIDN,
- ];
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $options = [
+ 'pattern' => new JsExpression($this->pattern),
+ 'fullPattern' => new JsExpression($this->fullPattern),
+ 'allowName' => $this->allowName,
+ 'message' => Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ ], Yii::$app->language),
+ 'enableIDN' => (boolean) $this->enableIDN,
+ ];
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
- ValidationAsset::register($view);
- if ($this->enableIDN) {
- PunycodeAsset::register($view);
- }
- return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');';
- }
+ ValidationAsset::register($view);
+ if ($this->enableIDN) {
+ PunycodeAsset::register($view);
+ }
+
+ return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');';
+ }
}
diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php
index 076066ba2f6..a714fc888ce 100644
--- a/framework/validators/ExistValidator.php
+++ b/framework/validators/ExistValidator.php
@@ -39,107 +39,108 @@
*/
class ExistValidator extends Validator
{
- /**
- * @var string the name of the ActiveRecord class that should be used to validate the existence
- * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
- * @see targetAttribute
- */
- public $targetClass;
- /**
- * @var string|array the name of the ActiveRecord attribute that should be used to
- * validate the existence of the current attribute value. If not set, it will use the name
- * of the attribute currently being validated. You may use an array to validate the existence
- * of multiple columns at the same time. The array values are the attributes that will be
- * used to validate the existence, while the array keys are the attributes whose values are to be validated.
- * If the key and the value are the same, you can just specify the value.
- */
- public $targetAttribute;
- /**
- * @var string|array|\Closure additional filter to be applied to the DB query used to check the existence of the attribute value.
- * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
- * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
- * is the [[\yii\db\Query|Query]] object that you can modify in the function.
- */
- public $filter;
+ /**
+ * @var string the name of the ActiveRecord class that should be used to validate the existence
+ * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
+ * @see targetAttribute
+ */
+ public $targetClass;
+ /**
+ * @var string|array the name of the ActiveRecord attribute that should be used to
+ * validate the existence of the current attribute value. If not set, it will use the name
+ * of the attribute currently being validated. You may use an array to validate the existence
+ * of multiple columns at the same time. The array values are the attributes that will be
+ * used to validate the existence, while the array keys are the attributes whose values are to be validated.
+ * If the key and the value are the same, you can just specify the value.
+ */
+ public $targetAttribute;
+ /**
+ * @var string|array|\Closure additional filter to be applied to the DB query used to check the existence of the attribute value.
+ * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
+ * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
+ * is the [[\yii\db\Query|Query]] object that you can modify in the function.
+ */
+ public $filter;
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} is invalid.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} is invalid.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
+ if (is_array($targetAttribute)) {
+ $params = [];
+ foreach ($targetAttribute as $k => $v) {
+ $params[$v] = is_integer($k) ? $object->$v : $object->$k;
+ }
+ } else {
+ $params = [$targetAttribute => $object->$attribute];
+ }
- if (is_array($targetAttribute)) {
- $params = [];
- foreach ($targetAttribute as $k => $v) {
- $params[$v] = is_integer($k) ? $object->$v : $object->$k;
- }
- } else {
- $params = [$targetAttribute => $object->$attribute];
- }
+ foreach ($params as $value) {
+ if (is_array($value)) {
+ $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- foreach ($params as $value) {
- if (is_array($value)) {
- $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- return;
- }
- }
+ return;
+ }
+ }
- $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
- $query = $this->createQuery($targetClass, $params);
+ $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
+ $query = $this->createQuery($targetClass, $params);
- if (!$query->exists()) {
- $this->addError($object, $attribute, $this->message);
- }
- }
+ if (!$query->exists()) {
+ $this->addError($object, $attribute, $this->message);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- if (is_array($value)) {
- return [$this->message, []];
- }
- if ($this->targetClass === null) {
- throw new InvalidConfigException('The "targetClass" property must be set.');
- }
- if (!is_string($this->targetAttribute)) {
- throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ if (is_array($value)) {
+ return [$this->message, []];
+ }
+ if ($this->targetClass === null) {
+ throw new InvalidConfigException('The "targetClass" property must be set.');
+ }
+ if (!is_string($this->targetAttribute)) {
+ throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
+ }
- $query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]);
+ $query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]);
- return $query->exists() ? null : [$this->message, []];
- }
+ return $query->exists() ? null : [$this->message, []];
+ }
- /**
- * Creates a query instance with the given condition.
- * @param string $targetClass the target AR class
- * @param mixed $condition query condition
- * @return \yii\db\ActiveQueryInterface the query instance
- */
- protected function createQuery($targetClass, $condition)
- {
- /** @var \yii\db\ActiveRecordInterface $targetClass */
- $query = $targetClass::find()->where($condition);
- if ($this->filter instanceof \Closure) {
- call_user_func($this->filter, $query);
- } elseif ($this->filter !== null) {
- $query->andWhere($this->filter);
- }
- return $query;
- }
+ /**
+ * Creates a query instance with the given condition.
+ * @param string $targetClass the target AR class
+ * @param mixed $condition query condition
+ * @return \yii\db\ActiveQueryInterface the query instance
+ */
+ protected function createQuery($targetClass, $condition)
+ {
+ /** @var \yii\db\ActiveRecordInterface $targetClass */
+ $query = $targetClass::find()->where($condition);
+ if ($this->filter instanceof \Closure) {
+ call_user_func($this->filter, $query);
+ } elseif ($this->filter !== null) {
+ $query->andWhere($this->filter);
+ }
+
+ return $query;
+ }
}
diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php
index bf565389166..86f0dd203e5 100644
--- a/framework/validators/FileValidator.php
+++ b/framework/validators/FileValidator.php
@@ -20,240 +20,243 @@
*/
class FileValidator extends Validator
{
- /**
- * @var array|string a list of file name extensions that are allowed to be uploaded.
- * This can be either an array or a string consisting of file extension names
- * separated by space or comma (e.g. "gif, jpg").
- * Extension names are case-insensitive. Defaults to null, meaning all file name
- * extensions are allowed.
- * @see wrongType
- */
- public $types;
- /**
- * @var integer the minimum number of bytes required for the uploaded file.
- * Defaults to null, meaning no limit.
- * @see tooSmall
- */
- public $minSize;
- /**
- * @var integer the maximum number of bytes required for the uploaded file.
- * Defaults to null, meaning no limit.
- * Note, the size limit is also affected by 'upload_max_filesize' INI setting
- * and the 'MAX_FILE_SIZE' hidden field value.
- * @see tooBig
- */
- public $maxSize;
- /**
- * @var integer the maximum file count the given attribute can hold.
- * It defaults to 1, meaning single file upload. By defining a higher number,
- * multiple uploads become possible.
- * @see tooMany
- */
- public $maxFiles = 1;
- /**
- * @var string the error message used when a file is not uploaded correctly.
- */
- public $message;
- /**
- * @var string the error message used when no file is uploaded.
- */
- public $uploadRequired;
- /**
- * @var string the error message used when the uploaded file is too large.
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {limit}: the maximum size allowed (see [[getSizeLimit()]])
- */
- public $tooBig;
- /**
- * @var string the error message used when the uploaded file is too small.
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {limit}: the value of [[minSize]]
- */
- public $tooSmall;
- /**
- * @var string the error message used when the uploaded file has an extension name
- * that is not listed in [[types]]. You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {extensions}: the list of the allowed extensions.
- */
- public $wrongType;
- /**
- * @var string the error message used if the count of multiple uploads exceeds limit.
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {limit}: the value of [[maxFiles]]
- */
- public $tooMany;
+ /**
+ * @var array|string a list of file name extensions that are allowed to be uploaded.
+ * This can be either an array or a string consisting of file extension names
+ * separated by space or comma (e.g. "gif, jpg").
+ * Extension names are case-insensitive. Defaults to null, meaning all file name
+ * extensions are allowed.
+ * @see wrongType
+ */
+ public $types;
+ /**
+ * @var integer the minimum number of bytes required for the uploaded file.
+ * Defaults to null, meaning no limit.
+ * @see tooSmall
+ */
+ public $minSize;
+ /**
+ * @var integer the maximum number of bytes required for the uploaded file.
+ * Defaults to null, meaning no limit.
+ * Note, the size limit is also affected by 'upload_max_filesize' INI setting
+ * and the 'MAX_FILE_SIZE' hidden field value.
+ * @see tooBig
+ */
+ public $maxSize;
+ /**
+ * @var integer the maximum file count the given attribute can hold.
+ * It defaults to 1, meaning single file upload. By defining a higher number,
+ * multiple uploads become possible.
+ * @see tooMany
+ */
+ public $maxFiles = 1;
+ /**
+ * @var string the error message used when a file is not uploaded correctly.
+ */
+ public $message;
+ /**
+ * @var string the error message used when no file is uploaded.
+ */
+ public $uploadRequired;
+ /**
+ * @var string the error message used when the uploaded file is too large.
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {limit}: the maximum size allowed (see [[getSizeLimit()]])
+ */
+ public $tooBig;
+ /**
+ * @var string the error message used when the uploaded file is too small.
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {limit}: the value of [[minSize]]
+ */
+ public $tooSmall;
+ /**
+ * @var string the error message used when the uploaded file has an extension name
+ * that is not listed in [[types]]. You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {extensions}: the list of the allowed extensions.
+ */
+ public $wrongType;
+ /**
+ * @var string the error message used if the count of multiple uploads exceeds limit.
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {limit}: the value of [[maxFiles]]
+ */
+ public $tooMany;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = Yii::t('yii', 'File upload failed.');
- }
- if ($this->uploadRequired === null) {
- $this->uploadRequired = Yii::t('yii', 'Please upload a file.');
- }
- if ($this->tooMany === null) {
- $this->tooMany = Yii::t('yii', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.');
- }
- if ($this->wrongType === null) {
- $this->wrongType = Yii::t('yii', 'Only files with these extensions are allowed: {extensions}.');
- }
- if ($this->tooBig === null) {
- $this->tooBig = Yii::t('yii', 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.');
- }
- if ($this->tooSmall === null) {
- $this->tooSmall = Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.');
- }
- if (!is_array($this->types)) {
- $this->types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY);
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', 'File upload failed.');
+ }
+ if ($this->uploadRequired === null) {
+ $this->uploadRequired = Yii::t('yii', 'Please upload a file.');
+ }
+ if ($this->tooMany === null) {
+ $this->tooMany = Yii::t('yii', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.');
+ }
+ if ($this->wrongType === null) {
+ $this->wrongType = Yii::t('yii', 'Only files with these extensions are allowed: {extensions}.');
+ }
+ if ($this->tooBig === null) {
+ $this->tooBig = Yii::t('yii', 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.');
+ }
+ if ($this->tooSmall === null) {
+ $this->tooSmall = Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.');
+ }
+ if (!is_array($this->types)) {
+ $this->types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ }
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- if ($this->maxFiles > 1) {
- $files = $object->$attribute;
- if (!is_array($files)) {
- $this->addError($object, $attribute, $this->uploadRequired);
- return;
- }
- foreach ($files as $i => $file) {
- if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) {
- unset($files[$i]);
- }
- }
- $object->$attribute = array_values($files);
- if (empty($files)) {
- $this->addError($object, $attribute, $this->uploadRequired);
- }
- if (count($files) > $this->maxFiles) {
- $this->addError($object, $attribute, $this->tooMany, ['limit' => $this->maxFiles]);
- } else {
- foreach ($files as $file) {
- $result = $this->validateValue($file);
- if (!empty($result)) {
- $this->addError($object, $attribute, $result[0], $result[1]);
- }
- }
- }
- } else {
- $result = $this->validateValue($object->$attribute);
- if (!empty($result)) {
- $this->addError($object, $attribute, $result[0], $result[1]);
- }
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ if ($this->maxFiles > 1) {
+ $files = $object->$attribute;
+ if (!is_array($files)) {
+ $this->addError($object, $attribute, $this->uploadRequired);
- /**
- * @inheritdoc
- */
- protected function validateValue($file)
- {
- if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) {
- return [$this->uploadRequired, []];
- }
- switch ($file->error) {
- case UPLOAD_ERR_OK:
- if ($this->maxSize !== null && $file->size > $this->maxSize) {
- return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]];
- } elseif ($this->minSize !== null && $file->size < $this->minSize) {
- return [$this->tooSmall, ['file' => $file->name, 'limit' => $this->minSize]];
- } elseif (!empty($this->types) && !in_array(strtolower(pathinfo($file->name, PATHINFO_EXTENSION)), $this->types, true)) {
- return [$this->wrongType, ['file' => $file->name, 'extensions' => implode(', ', $this->types)]];
- } else {
- return null;
- }
- case UPLOAD_ERR_INI_SIZE:
- case UPLOAD_ERR_FORM_SIZE:
- return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]];
- case UPLOAD_ERR_PARTIAL:
- Yii::warning('File was only partially uploaded: ' . $file->name, __METHOD__);
- break;
- case UPLOAD_ERR_NO_TMP_DIR:
- Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->name, __METHOD__);
- break;
- case UPLOAD_ERR_CANT_WRITE:
- Yii::warning('Failed to write the uploaded file to disk: ' . $file->name, __METHOD__);
- break;
- case UPLOAD_ERR_EXTENSION:
- Yii::warning('File upload was stopped by some PHP extension: ' . $file->name, __METHOD__);
- break;
- default:
- break;
- }
- return [$this->message, []];
- }
+ return;
+ }
+ foreach ($files as $i => $file) {
+ if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) {
+ unset($files[$i]);
+ }
+ }
+ $object->$attribute = array_values($files);
+ if (empty($files)) {
+ $this->addError($object, $attribute, $this->uploadRequired);
+ }
+ if (count($files) > $this->maxFiles) {
+ $this->addError($object, $attribute, $this->tooMany, ['limit' => $this->maxFiles]);
+ } else {
+ foreach ($files as $file) {
+ $result = $this->validateValue($file);
+ if (!empty($result)) {
+ $this->addError($object, $attribute, $result[0], $result[1]);
+ }
+ }
+ }
+ } else {
+ $result = $this->validateValue($object->$attribute);
+ if (!empty($result)) {
+ $this->addError($object, $attribute, $result[0], $result[1]);
+ }
+ }
+ }
- /**
- * Returns the maximum size allowed for uploaded files.
- * This is determined based on three factors:
- *
- * - 'upload_max_filesize' in php.ini
- * - 'MAX_FILE_SIZE' hidden field
- * - [[maxSize]]
- *
- * @return integer the size limit for uploaded files.
- */
- public function getSizeLimit()
- {
- $limit = ini_get('upload_max_filesize');
- $limit = $this->sizeToBytes($limit);
- if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) {
- $limit = $this->maxSize;
- }
- if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) {
- $limit = (int)$_POST['MAX_FILE_SIZE'];
- }
- return $limit;
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($file)
+ {
+ if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) {
+ return [$this->uploadRequired, []];
+ }
+ switch ($file->error) {
+ case UPLOAD_ERR_OK:
+ if ($this->maxSize !== null && $file->size > $this->maxSize) {
+ return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]];
+ } elseif ($this->minSize !== null && $file->size < $this->minSize) {
+ return [$this->tooSmall, ['file' => $file->name, 'limit' => $this->minSize]];
+ } elseif (!empty($this->types) && !in_array(strtolower(pathinfo($file->name, PATHINFO_EXTENSION)), $this->types, true)) {
+ return [$this->wrongType, ['file' => $file->name, 'extensions' => implode(', ', $this->types)]];
+ } else {
+ return null;
+ }
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]];
+ case UPLOAD_ERR_PARTIAL:
+ Yii::warning('File was only partially uploaded: ' . $file->name, __METHOD__);
+ break;
+ case UPLOAD_ERR_NO_TMP_DIR:
+ Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->name, __METHOD__);
+ break;
+ case UPLOAD_ERR_CANT_WRITE:
+ Yii::warning('Failed to write the uploaded file to disk: ' . $file->name, __METHOD__);
+ break;
+ case UPLOAD_ERR_EXTENSION:
+ Yii::warning('File upload was stopped by some PHP extension: ' . $file->name, __METHOD__);
+ break;
+ default:
+ break;
+ }
- /**
- * @inheritdoc
- */
- public function isEmpty($value, $trim = false)
- {
- $value = is_array($value) && !empty($value) ? $value[0] : $value;
+ return [$this->message, []];
+ }
- return !$value instanceof UploadedFile || $value->error == UPLOAD_ERR_NO_FILE;
- }
+ /**
+ * Returns the maximum size allowed for uploaded files.
+ * This is determined based on three factors:
+ *
+ * - 'upload_max_filesize' in php.ini
+ * - 'MAX_FILE_SIZE' hidden field
+ * - [[maxSize]]
+ *
+ * @return integer the size limit for uploaded files.
+ */
+ public function getSizeLimit()
+ {
+ $limit = ini_get('upload_max_filesize');
+ $limit = $this->sizeToBytes($limit);
+ if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) {
+ $limit = $this->maxSize;
+ }
+ if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) {
+ $limit = (int) $_POST['MAX_FILE_SIZE'];
+ }
- /**
- * Converts php.ini style size to bytes
- *
- * @param string $sizeStr $sizeStr
- * @return int
- */
- private function sizeToBytes($sizeStr)
- {
- switch (substr($sizeStr, -1)) {
- case 'M':
- case 'm':
- return (int)$sizeStr * 1048576;
- case 'K':
- case 'k':
- return (int)$sizeStr * 1024;
- case 'G':
- case 'g':
- return (int)$sizeStr * 1073741824;
- default:
- return (int)$sizeStr;
- }
- }
+ return $limit;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function isEmpty($value, $trim = false)
+ {
+ $value = is_array($value) && !empty($value) ? $value[0] : $value;
+
+ return !$value instanceof UploadedFile || $value->error == UPLOAD_ERR_NO_FILE;
+ }
+
+ /**
+ * Converts php.ini style size to bytes
+ *
+ * @param string $sizeStr $sizeStr
+ * @return int
+ */
+ private function sizeToBytes($sizeStr)
+ {
+ switch (substr($sizeStr, -1)) {
+ case 'M':
+ case 'm':
+ return (int) $sizeStr * 1048576;
+ case 'K':
+ case 'k':
+ return (int) $sizeStr * 1024;
+ case 'G':
+ case 'g':
+ return (int) $sizeStr * 1073741824;
+ default:
+ return (int) $sizeStr;
+ }
+ }
}
diff --git a/framework/validators/FilterValidator.php b/framework/validators/FilterValidator.php
index 06a52d9eee8..b196b4ddaf3 100644
--- a/framework/validators/FilterValidator.php
+++ b/framework/validators/FilterValidator.php
@@ -30,37 +30,37 @@
*/
class FilterValidator extends Validator
{
- /**
- * @var callable the filter. This can be a global function name, anonymous function, etc.
- * The function signature must be as follows,
- *
- * ~~~
- * function foo($value) {...return $newValue; }
- * ~~~
- */
- public $filter;
- /**
- * @var boolean this property is overwritten to be false so that this validator will
- * be applied when the value being validated is empty.
- */
- public $skipOnEmpty = false;
+ /**
+ * @var callable the filter. This can be a global function name, anonymous function, etc.
+ * The function signature must be as follows,
+ *
+ * ~~~
+ * function foo($value) {...return $newValue; }
+ * ~~~
+ */
+ public $filter;
+ /**
+ * @var boolean this property is overwritten to be false so that this validator will
+ * be applied when the value being validated is empty.
+ */
+ public $skipOnEmpty = false;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->filter === null) {
- throw new InvalidConfigException('The "filter" property must be set.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->filter === null) {
+ throw new InvalidConfigException('The "filter" property must be set.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $object->$attribute = call_user_func($this->filter, $object->$attribute);
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $object->$attribute = call_user_func($this->filter, $object->$attribute);
+ }
}
diff --git a/framework/validators/ImageValidator.php b/framework/validators/ImageValidator.php
index fa9587d57a8..ebae6f17621 100644
--- a/framework/validators/ImageValidator.php
+++ b/framework/validators/ImageValidator.php
@@ -21,170 +21,172 @@
*/
class ImageValidator extends FileValidator
{
- /**
- * @var string the error message used when the uploaded file is not an image.
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- */
- public $notImage;
- /**
- * @var integer the minimum width in pixels.
- * Defaults to null, meaning no limit.
- * @see underWidth
- */
- public $minWidth;
- /**
- * @var integer the maximum width in pixels.
- * Defaults to null, meaning no limit.
- * @see overWidth
- */
- public $maxWidth;
- /**
- * @var integer the minimum height in pixels.
- * Defaults to null, meaning no limit.
- * @see underHeight
- */
- public $minHeight;
- /**
- * @var integer the maximum width in pixels.
- * Defaults to null, meaning no limit.
- * @see overWidth
- */
- public $maxHeight;
- /**
- * @var array|string a list of file mime types that are allowed to be uploaded.
- * This can be either an array or a string consisting of file mime types
- * separated by space or comma (e.g. "image/jpeg, image/png").
- * Mime type names are case-insensitive. Defaults to null, meaning all mime types
- * are allowed.
- * @see wrongMimeType
- */
- public $mimeTypes;
- /**
- * @var string the error message used when the image is under [[minWidth]].
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {limit}: the value of [[minWidth]]
- */
- public $underWidth;
- /**
- * @var string the error message used when the image is over [[maxWidth]].
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {limit}: the value of [[maxWidth]]
- */
- public $overWidth;
- /**
- * @var string the error message used when the image is under [[minHeight]].
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {limit}: the value of [[minHeight]]
- */
- public $underHeight;
- /**
- * @var string the error message used when the image is over [[maxHeight]].
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {limit}: the value of [[maxHeight]]
- */
- public $overHeight;
- /**
- * @var string the error message used when the file has an mime type
- * that is not listed in [[mimeTypes]].
- * You may use the following tokens in the message:
- *
- * - {attribute}: the attribute name
- * - {file}: the uploaded file name
- * - {mimeTypes}: the value of [[mimeTypes]]
- */
- public $wrongMimeType;
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
-
- if ($this->notImage === null) {
- $this->notImage = Yii::t('yii', 'The file "{file}" is not an image.');
- }
- if ($this->underWidth === null) {
- $this->underWidth = Yii::t('yii', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
- }
- if ($this->underHeight === null) {
- $this->underHeight = Yii::t('yii', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
- }
- if ($this->overWidth === null) {
- $this->overWidth = Yii::t('yii', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
- }
- if ($this->overHeight === null) {
- $this->overHeight = Yii::t('yii', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
- }
- if ($this->wrongMimeType === null) {
- $this->wrongMimeType = Yii::t('yii', 'Only files with these mimeTypes are allowed: {mimeTypes}.');
- }
- if (!is_array($this->mimeTypes)) {
- $this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY);
- }
- }
-
- /**
- * @inheritdoc
- */
- protected function validateValue($file)
- {
- $result = parent::validateValue($file);
- return empty($result) ? $this->validateImage($file) : $result;
- }
-
- /**
- * Validates an image file.
- * @param UploadedFile $image uploaded file passed to check against a set of rules
- * @return array|null the error message and the parameters to be inserted into the error message.
- * Null should be returned if the data is valid.
- */
- protected function validateImage($image)
- {
- if (!empty($this->mimeTypes) && !in_array(FileHelper::getMimeType($image->tempName), $this->mimeTypes, true)) {
- return [$this->wrongMimeType, ['file' => $image->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]];
- }
-
- if (false === ($imageInfo = getimagesize($image->tempName))) {
- return [$this->notImage, ['file' => $image->name]];
- }
-
- list($width, $height, $type) = $imageInfo;
-
- if ($width == 0 || $height == 0) {
- return [$this->notImage, ['file' => $image->name]];
- }
-
- if ($this->minWidth !== null && $width < $this->minWidth) {
- return [$this->underWidth, ['file' => $image->name, 'limit' => $this->minWidth]];
- }
-
- if ($this->minHeight !== null && $height < $this->minHeight) {
- return [$this->underHeight, ['file' => $image->name, 'limit' => $this->minHeight]];
- }
-
- if ($this->maxWidth !== null && $width > $this->maxWidth) {
- return [$this->overWidth, ['file' => $image->name, 'limit' => $this->maxWidth]];
- }
-
- if ($this->maxHeight !== null && $height > $this->maxHeight) {
- return [$this->overHeight, ['file' => $image->name, 'limit' => $this->maxHeight]];
- }
- return null;
- }
+ /**
+ * @var string the error message used when the uploaded file is not an image.
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ */
+ public $notImage;
+ /**
+ * @var integer the minimum width in pixels.
+ * Defaults to null, meaning no limit.
+ * @see underWidth
+ */
+ public $minWidth;
+ /**
+ * @var integer the maximum width in pixels.
+ * Defaults to null, meaning no limit.
+ * @see overWidth
+ */
+ public $maxWidth;
+ /**
+ * @var integer the minimum height in pixels.
+ * Defaults to null, meaning no limit.
+ * @see underHeight
+ */
+ public $minHeight;
+ /**
+ * @var integer the maximum width in pixels.
+ * Defaults to null, meaning no limit.
+ * @see overWidth
+ */
+ public $maxHeight;
+ /**
+ * @var array|string a list of file mime types that are allowed to be uploaded.
+ * This can be either an array or a string consisting of file mime types
+ * separated by space or comma (e.g. "image/jpeg, image/png").
+ * Mime type names are case-insensitive. Defaults to null, meaning all mime types
+ * are allowed.
+ * @see wrongMimeType
+ */
+ public $mimeTypes;
+ /**
+ * @var string the error message used when the image is under [[minWidth]].
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {limit}: the value of [[minWidth]]
+ */
+ public $underWidth;
+ /**
+ * @var string the error message used when the image is over [[maxWidth]].
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {limit}: the value of [[maxWidth]]
+ */
+ public $overWidth;
+ /**
+ * @var string the error message used when the image is under [[minHeight]].
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {limit}: the value of [[minHeight]]
+ */
+ public $underHeight;
+ /**
+ * @var string the error message used when the image is over [[maxHeight]].
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {limit}: the value of [[maxHeight]]
+ */
+ public $overHeight;
+ /**
+ * @var string the error message used when the file has an mime type
+ * that is not listed in [[mimeTypes]].
+ * You may use the following tokens in the message:
+ *
+ * - {attribute}: the attribute name
+ * - {file}: the uploaded file name
+ * - {mimeTypes}: the value of [[mimeTypes]]
+ */
+ public $wrongMimeType;
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+
+ if ($this->notImage === null) {
+ $this->notImage = Yii::t('yii', 'The file "{file}" is not an image.');
+ }
+ if ($this->underWidth === null) {
+ $this->underWidth = Yii::t('yii', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
+ }
+ if ($this->underHeight === null) {
+ $this->underHeight = Yii::t('yii', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
+ }
+ if ($this->overWidth === null) {
+ $this->overWidth = Yii::t('yii', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
+ }
+ if ($this->overHeight === null) {
+ $this->overHeight = Yii::t('yii', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
+ }
+ if ($this->wrongMimeType === null) {
+ $this->wrongMimeType = Yii::t('yii', 'Only files with these mimeTypes are allowed: {mimeTypes}.');
+ }
+ if (!is_array($this->mimeTypes)) {
+ $this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($file)
+ {
+ $result = parent::validateValue($file);
+
+ return empty($result) ? $this->validateImage($file) : $result;
+ }
+
+ /**
+ * Validates an image file.
+ * @param UploadedFile $image uploaded file passed to check against a set of rules
+ * @return array|null the error message and the parameters to be inserted into the error message.
+ * Null should be returned if the data is valid.
+ */
+ protected function validateImage($image)
+ {
+ if (!empty($this->mimeTypes) && !in_array(FileHelper::getMimeType($image->tempName), $this->mimeTypes, true)) {
+ return [$this->wrongMimeType, ['file' => $image->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]];
+ }
+
+ if (false === ($imageInfo = getimagesize($image->tempName))) {
+ return [$this->notImage, ['file' => $image->name]];
+ }
+
+ list($width, $height, $type) = $imageInfo;
+
+ if ($width == 0 || $height == 0) {
+ return [$this->notImage, ['file' => $image->name]];
+ }
+
+ if ($this->minWidth !== null && $width < $this->minWidth) {
+ return [$this->underWidth, ['file' => $image->name, 'limit' => $this->minWidth]];
+ }
+
+ if ($this->minHeight !== null && $height < $this->minHeight) {
+ return [$this->underHeight, ['file' => $image->name, 'limit' => $this->minHeight]];
+ }
+
+ if ($this->maxWidth !== null && $width > $this->maxWidth) {
+ return [$this->overWidth, ['file' => $image->name, 'limit' => $this->maxWidth]];
+ }
+
+ if ($this->maxHeight !== null && $height > $this->maxHeight) {
+ return [$this->overHeight, ['file' => $image->name, 'limit' => $this->maxHeight]];
+ }
+
+ return null;
+ }
}
diff --git a/framework/validators/InlineValidator.php b/framework/validators/InlineValidator.php
index b769e4e75f5..26bdc2dde72 100644
--- a/framework/validators/InlineValidator.php
+++ b/framework/validators/InlineValidator.php
@@ -24,61 +24,62 @@
*/
class InlineValidator extends Validator
{
- /**
- * @var string|\Closure an anonymous function or the name of a model class method that will be
- * called to perform the actual validation. The signature of the method should be like the following:
- *
- * ~~~
- * function foo($attribute, $params)
- * ~~~
- */
- public $method;
- /**
- * @var array additional parameters that are passed to the validation method
- */
- public $params;
- /**
- * @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code.
- * The signature of the method should be like the following:
- *
- * ~~~
- * function foo($attribute, $params)
- * {
- * return "javascript";
- * }
- * ~~~
- *
- * where `$attribute` refers to the attribute name to be validated.
- *
- * Please refer to [[clientValidateAttribute()]] for details on how to return client validation code.
- */
- public $clientValidate;
+ /**
+ * @var string|\Closure an anonymous function or the name of a model class method that will be
+ * called to perform the actual validation. The signature of the method should be like the following:
+ *
+ * ~~~
+ * function foo($attribute, $params)
+ * ~~~
+ */
+ public $method;
+ /**
+ * @var array additional parameters that are passed to the validation method
+ */
+ public $params;
+ /**
+ * @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code.
+ * The signature of the method should be like the following:
+ *
+ * ~~~
+ * function foo($attribute, $params)
+ * {
+ * return "javascript";
+ * }
+ * ~~~
+ *
+ * where `$attribute` refers to the attribute name to be validated.
+ *
+ * Please refer to [[clientValidateAttribute()]] for details on how to return client validation code.
+ */
+ public $clientValidate;
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $method = $this->method;
- if (is_string($method)) {
- $method = [$object, $method];
- }
- call_user_func($method, $attribute, $this->params);
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $method = $this->method;
+ if (is_string($method)) {
+ $method = [$object, $method];
+ }
+ call_user_func($method, $attribute, $this->params);
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- if ($this->clientValidate !== null) {
- $method = $this->clientValidate;
- if (is_string($method)) {
- $method = [$object, $method];
- }
- return call_user_func($method, $attribute, $this->params);
- } else {
- return null;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ if ($this->clientValidate !== null) {
+ $method = $this->clientValidate;
+ if (is_string($method)) {
+ $method = [$object, $method];
+ }
+
+ return call_user_func($method, $attribute, $this->params);
+ } else {
+ return null;
+ }
+ }
}
diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php
index d18b5cc5cb0..ff3a71444db 100644
--- a/framework/validators/NumberValidator.php
+++ b/framework/validators/NumberValidator.php
@@ -23,130 +23,131 @@
*/
class NumberValidator extends Validator
{
- /**
- * @var boolean whether the attribute value can only be an integer. Defaults to false.
- */
- public $integerOnly = false;
- /**
- * @var integer|float upper limit of the number. Defaults to null, meaning no upper limit.
- */
- public $max;
- /**
- * @var integer|float lower limit of the number. Defaults to null, meaning no lower limit.
- */
- public $min;
- /**
- * @var string user-defined error message used when the value is bigger than [[max]].
- */
- public $tooBig;
- /**
- * @var string user-defined error message used when the value is smaller than [[min]].
- */
- public $tooSmall;
- /**
- * @var string the regular expression for matching integers.
- */
- public $integerPattern = '/^\s*[+-]?\d+\s*$/';
- /**
- * @var string the regular expression for matching numbers. It defaults to a pattern
- * that matches floating numbers with optional exponential part (e.g. -1.23e-10).
- */
- public $numberPattern = '/^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$/';
+ /**
+ * @var boolean whether the attribute value can only be an integer. Defaults to false.
+ */
+ public $integerOnly = false;
+ /**
+ * @var integer|float upper limit of the number. Defaults to null, meaning no upper limit.
+ */
+ public $max;
+ /**
+ * @var integer|float lower limit of the number. Defaults to null, meaning no lower limit.
+ */
+ public $min;
+ /**
+ * @var string user-defined error message used when the value is bigger than [[max]].
+ */
+ public $tooBig;
+ /**
+ * @var string user-defined error message used when the value is smaller than [[min]].
+ */
+ public $tooSmall;
+ /**
+ * @var string the regular expression for matching integers.
+ */
+ public $integerPattern = '/^\s*[+-]?\d+\s*$/';
+ /**
+ * @var string the regular expression for matching numbers. It defaults to a pattern
+ * that matches floating numbers with optional exponential part (e.g. -1.23e-10).
+ */
+ public $numberPattern = '/^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$/';
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = $this->integerOnly ? Yii::t('yii', '{attribute} must be an integer.')
+ : Yii::t('yii', '{attribute} must be a number.');
+ }
+ if ($this->min !== null && $this->tooSmall === null) {
+ $this->tooSmall = Yii::t('yii', '{attribute} must be no less than {min}.');
+ }
+ if ($this->max !== null && $this->tooBig === null) {
+ $this->tooBig = Yii::t('yii', '{attribute} must be no greater than {max}.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = $this->integerOnly ? Yii::t('yii', '{attribute} must be an integer.')
- : Yii::t('yii', '{attribute} must be a number.');
- }
- if ($this->min !== null && $this->tooSmall === null) {
- $this->tooSmall = Yii::t('yii', '{attribute} must be no less than {min}.');
- }
- if ($this->max !== null && $this->tooBig === null) {
- $this->tooBig = Yii::t('yii', '{attribute} must be no greater than {max}.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $value = $object->$attribute;
+ if (is_array($value)) {
+ $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $value = $object->$attribute;
- if (is_array($value)) {
- $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- return;
- }
- $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
- if (!preg_match($pattern, "$value")) {
- $this->addError($object, $attribute, $this->message);
- }
- if ($this->min !== null && $value < $this->min) {
- $this->addError($object, $attribute, $this->tooSmall, ['min' => $this->min]);
- }
- if ($this->max !== null && $value > $this->max) {
- $this->addError($object, $attribute, $this->tooBig, ['max' => $this->max]);
- }
- }
+ return;
+ }
+ $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
+ if (!preg_match($pattern, "$value")) {
+ $this->addError($object, $attribute, $this->message);
+ }
+ if ($this->min !== null && $value < $this->min) {
+ $this->addError($object, $attribute, $this->tooSmall, ['min' => $this->min]);
+ }
+ if ($this->max !== null && $value > $this->max) {
+ $this->addError($object, $attribute, $this->tooBig, ['max' => $this->max]);
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- if (is_array($value)) {
- return [Yii::t('yii', '{attribute} is invalid.'), []];
- }
- $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
- if (!preg_match($pattern, "$value")) {
- return [$this->message, []];
- } elseif ($this->min !== null && $value < $this->min) {
- return [$this->tooSmall, ['min' => $this->min]];
- } elseif ($this->max !== null && $value > $this->max) {
- return [$this->tooBig, ['max' => $this->max]];
- } else {
- return null;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ if (is_array($value)) {
+ return [Yii::t('yii', '{attribute} is invalid.'), []];
+ }
+ $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
+ if (!preg_match($pattern, "$value")) {
+ return [$this->message, []];
+ } elseif ($this->min !== null && $value < $this->min) {
+ return [$this->tooSmall, ['min' => $this->min]];
+ } elseif ($this->max !== null && $value > $this->max) {
+ return [$this->tooBig, ['max' => $this->max]];
+ } else {
+ return null;
+ }
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $label = $object->getAttributeLabel($attribute);
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $label = $object->getAttributeLabel($attribute);
- $options = [
- 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern),
- 'message' => Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $label,
- ], Yii::$app->language),
- ];
+ $options = [
+ 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern),
+ 'message' => Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $label,
+ ], Yii::$app->language),
+ ];
- if ($this->min !== null) {
- $options['min'] = $this->min;
- $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [
- 'attribute' => $label,
- 'min' => $this->min,
- ], Yii::$app->language);
- }
- if ($this->max !== null) {
- $options['max'] = $this->max;
- $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [
- 'attribute' => $label,
- 'max' => $this->max,
- ], Yii::$app->language);
- }
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
+ if ($this->min !== null) {
+ $options['min'] = $this->min;
+ $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [
+ 'attribute' => $label,
+ 'min' => $this->min,
+ ], Yii::$app->language);
+ }
+ if ($this->max !== null) {
+ $options['max'] = $this->max;
+ $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [
+ 'attribute' => $label,
+ 'max' => $this->max,
+ ], Yii::$app->language);
+ }
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
- ValidationAsset::register($view);
- return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');';
- }
+ ValidationAsset::register($view);
+
+ return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');';
+ }
}
diff --git a/framework/validators/PunycodeAsset.php b/framework/validators/PunycodeAsset.php
index 5f6a4110cd8..1988da5e8a5 100644
--- a/framework/validators/PunycodeAsset.php
+++ b/framework/validators/PunycodeAsset.php
@@ -17,8 +17,8 @@
*/
class PunycodeAsset extends AssetBundle
{
- public $sourcePath = '@yii/assets';
- public $js = [
- 'punycode/punycode.js',
- ];
+ public $sourcePath = '@yii/assets';
+ public $js = [
+ 'punycode/punycode.js',
+ ];
}
diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php
index e376a23407c..18f1864fd9f 100644
--- a/framework/validators/RangeValidator.php
+++ b/framework/validators/RangeValidator.php
@@ -22,65 +22,67 @@
*/
class RangeValidator extends Validator
{
- /**
- * @var array list of valid values that the attribute value should be among
- */
- public $range;
- /**
- * @var boolean whether the comparison is strict (both type and value must be the same)
- */
- public $strict = false;
- /**
- * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
- * the attribute value should NOT be among the list of values defined via [[range]].
- **/
- public $not = false;
+ /**
+ * @var array list of valid values that the attribute value should be among
+ */
+ public $range;
+ /**
+ * @var boolean whether the comparison is strict (both type and value must be the same)
+ */
+ public $strict = false;
+ /**
+ * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
+ * the attribute value should NOT be among the list of values defined via [[range]].
+ **/
+ public $not = false;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if (!is_array($this->range)) {
- throw new InvalidConfigException('The "range" property must be set.');
- }
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} is invalid.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if (!is_array($this->range)) {
+ throw new InvalidConfigException('The "range" property must be set.');
+ }
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} is invalid.');
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- $valid = !$this->not && in_array($value, $this->range, $this->strict)
- || $this->not && !in_array($value, $this->range, $this->strict);
- return $valid ? null : [$this->message, []];
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ $valid = !$this->not && in_array($value, $this->range, $this->strict)
+ || $this->not && !in_array($value, $this->range, $this->strict);
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $range = [];
- foreach ($this->range as $value) {
- $range[] = (string)$value;
- }
- $options = [
- 'range' => $range,
- 'not' => $this->not,
- 'message' => Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $object->getAttributeLabel($attribute),
- ], Yii::$app->language),
- ];
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
+ return $valid ? null : [$this->message, []];
+ }
- ValidationAsset::register($view);
- return 'yii.validation.range(value, messages, ' . json_encode($options) . ');';
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $range = [];
+ foreach ($this->range as $value) {
+ $range[] = (string) $value;
+ }
+ $options = [
+ 'range' => $range,
+ 'not' => $this->not,
+ 'message' => Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ ], Yii::$app->language),
+ ];
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+
+ ValidationAsset::register($view);
+
+ return 'yii.validation.range(value, messages, ' . json_encode($options) . ');';
+ }
}
diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php
index 68dc9e545a0..d46344958c8 100644
--- a/framework/validators/RegularExpressionValidator.php
+++ b/framework/validators/RegularExpressionValidator.php
@@ -22,72 +22,74 @@
*/
class RegularExpressionValidator extends Validator
{
- /**
- * @var string the regular expression to be matched with
- */
- public $pattern;
- /**
- * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
- * the regular expression defined via [[pattern]] should NOT match the attribute value.
- **/
- public $not = false;
+ /**
+ * @var string the regular expression to be matched with
+ */
+ public $pattern;
+ /**
+ * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
+ * the regular expression defined via [[pattern]] should NOT match the attribute value.
+ **/
+ public $not = false;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->pattern === null) {
- throw new InvalidConfigException('The "pattern" property must be set.');
- }
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} is invalid.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->pattern === null) {
+ throw new InvalidConfigException('The "pattern" property must be set.');
+ }
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} is invalid.');
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- $valid = !is_array($value) &&
- (!$this->not && preg_match($this->pattern, $value)
- || $this->not && !preg_match($this->pattern, $value));
- return $valid ? null : [$this->message, []];
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ $valid = !is_array($value) &&
+ (!$this->not && preg_match($this->pattern, $value)
+ || $this->not && !preg_match($this->pattern, $value));
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $pattern = $this->pattern;
- $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern);
- $deliminator = substr($pattern, 0, 1);
- $pos = strrpos($pattern, $deliminator, 1);
- $flag = substr($pattern, $pos + 1);
- if ($deliminator !== '/') {
- $pattern = '/' . str_replace('/', '\\/', substr($pattern, 1, $pos - 1)) . '/';
- } else {
- $pattern = substr($pattern, 0, $pos + 1);
- }
- if (!empty($flag)) {
- $pattern .= preg_replace('/[^igm]/', '', $flag);
- }
+ return $valid ? null : [$this->message, []];
+ }
- $options = [
- 'pattern' => new JsExpression($pattern),
- 'not' => $this->not,
- 'message' => Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $object->getAttributeLabel($attribute),
- ], Yii::$app->language),
- ];
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $pattern = $this->pattern;
+ $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern);
+ $deliminator = substr($pattern, 0, 1);
+ $pos = strrpos($pattern, $deliminator, 1);
+ $flag = substr($pattern, $pos + 1);
+ if ($deliminator !== '/') {
+ $pattern = '/' . str_replace('/', '\\/', substr($pattern, 1, $pos - 1)) . '/';
+ } else {
+ $pattern = substr($pattern, 0, $pos + 1);
+ }
+ if (!empty($flag)) {
+ $pattern .= preg_replace('/[^igm]/', '', $flag);
+ }
- ValidationAsset::register($view);
- return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');';
- }
+ $options = [
+ 'pattern' => new JsExpression($pattern),
+ 'not' => $this->not,
+ 'message' => Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ ], Yii::$app->language),
+ ];
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+
+ ValidationAsset::register($view);
+
+ return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');';
+ }
}
diff --git a/framework/validators/RequiredValidator.php b/framework/validators/RequiredValidator.php
index 72618f9d7ad..2bbe27dc0ac 100644
--- a/framework/validators/RequiredValidator.php
+++ b/framework/validators/RequiredValidator.php
@@ -17,94 +17,95 @@
*/
class RequiredValidator extends Validator
{
- /**
- * @var boolean whether to skip this validator if the value being validated is empty.
- */
- public $skipOnEmpty = false;
- /**
- * @var mixed the desired value that the attribute must have.
- * If this is null, the validator will validate that the specified attribute is not empty.
- * If this is set as a value that is not null, the validator will validate that
- * the attribute has a value that is the same as this property value.
- * Defaults to null.
- * @see strict
- */
- public $requiredValue;
- /**
- * @var boolean whether the comparison between the attribute value and [[requiredValue]] is strict.
- * When this is true, both the values and types must match.
- * Defaults to false, meaning only the values need to match.
- * Note that when [[requiredValue]] is null, if this property is true, the validator will check
- * if the attribute value is null; If this property is false, the validator will call [[isEmpty]]
- * to check if the attribute value is empty.
- */
- public $strict = false;
- /**
- * @var string the user-defined error message. It may contain the following placeholders which
- * will be replaced accordingly by the validator:
- *
- * - `{attribute}`: the label of the attribute being validated
- * - `{value}`: the value of the attribute being validated
- * - `{requiredValue}`: the value of [[requiredValue]]
- */
- public $message;
+ /**
+ * @var boolean whether to skip this validator if the value being validated is empty.
+ */
+ public $skipOnEmpty = false;
+ /**
+ * @var mixed the desired value that the attribute must have.
+ * If this is null, the validator will validate that the specified attribute is not empty.
+ * If this is set as a value that is not null, the validator will validate that
+ * the attribute has a value that is the same as this property value.
+ * Defaults to null.
+ * @see strict
+ */
+ public $requiredValue;
+ /**
+ * @var boolean whether the comparison between the attribute value and [[requiredValue]] is strict.
+ * When this is true, both the values and types must match.
+ * Defaults to false, meaning only the values need to match.
+ * Note that when [[requiredValue]] is null, if this property is true, the validator will check
+ * if the attribute value is null; If this property is false, the validator will call [[isEmpty]]
+ * to check if the attribute value is empty.
+ */
+ public $strict = false;
+ /**
+ * @var string the user-defined error message. It may contain the following placeholders which
+ * will be replaced accordingly by the validator:
+ *
+ * - `{attribute}`: the label of the attribute being validated
+ * - `{value}`: the value of the attribute being validated
+ * - `{requiredValue}`: the value of [[requiredValue]]
+ */
+ public $message;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = $this->requiredValue === null ? Yii::t('yii', '{attribute} cannot be blank.')
- : Yii::t('yii', '{attribute} must be "{requiredValue}".');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = $this->requiredValue === null ? Yii::t('yii', '{attribute} cannot be blank.')
+ : Yii::t('yii', '{attribute} must be "{requiredValue}".');
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- if ($this->requiredValue === null) {
- if ($this->strict && $value !== null || !$this->strict && !$this->isEmpty($value, true)) {
- return null;
- }
- } elseif (!$this->strict && $value == $this->requiredValue || $this->strict && $value === $this->requiredValue) {
- return null;
- }
- if ($this->requiredValue === null) {
- return [$this->message, []];
- } else {
- return [$this->message, [
- 'requiredValue' => $this->requiredValue,
- ]];
- }
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ if ($this->requiredValue === null) {
+ if ($this->strict && $value !== null || !$this->strict && !$this->isEmpty($value, true)) {
+ return null;
+ }
+ } elseif (!$this->strict && $value == $this->requiredValue || $this->strict && $value === $this->requiredValue) {
+ return null;
+ }
+ if ($this->requiredValue === null) {
+ return [$this->message, []];
+ } else {
+ return [$this->message, [
+ 'requiredValue' => $this->requiredValue,
+ ]];
+ }
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $options = [];
- if ($this->requiredValue !== null) {
- $options['message'] = Yii::$app->getI18n()->format($this->message, [
- 'requiredValue' => $this->requiredValue,
- ], Yii::$app->language);
- $options['requiredValue'] = $this->requiredValue;
- } else {
- $options['message'] = $this->message;
- }
- if ($this->strict) {
- $options['strict'] = 1;
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $options = [];
+ if ($this->requiredValue !== null) {
+ $options['message'] = Yii::$app->getI18n()->format($this->message, [
+ 'requiredValue' => $this->requiredValue,
+ ], Yii::$app->language);
+ $options['requiredValue'] = $this->requiredValue;
+ } else {
+ $options['message'] = $this->message;
+ }
+ if ($this->strict) {
+ $options['strict'] = 1;
+ }
- $options['message'] = Yii::$app->getI18n()->format($options['message'], [
- 'attribute' => $object->getAttributeLabel($attribute),
- ], Yii::$app->language);
+ $options['message'] = Yii::$app->getI18n()->format($options['message'], [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ ], Yii::$app->language);
- ValidationAsset::register($view);
- return 'yii.validation.required(value, messages, ' . json_encode($options) . ');';
- }
+ ValidationAsset::register($view);
+
+ return 'yii.validation.required(value, messages, ' . json_encode($options) . ');';
+ }
}
diff --git a/framework/validators/SafeValidator.php b/framework/validators/SafeValidator.php
index 25da899ce14..fcb8440ec51 100644
--- a/framework/validators/SafeValidator.php
+++ b/framework/validators/SafeValidator.php
@@ -15,10 +15,10 @@
*/
class SafeValidator extends Validator
{
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ }
}
diff --git a/framework/validators/StringValidator.php b/framework/validators/StringValidator.php
index 6deea397ac1..14a30eb0c49 100644
--- a/framework/validators/StringValidator.php
+++ b/framework/validators/StringValidator.php
@@ -19,168 +19,169 @@
*/
class StringValidator extends Validator
{
- /**
- * @var integer|array specifies the length limit of the value to be validated.
- * This can be specified in one of the following forms:
- *
- * - an integer: the exact length that the value should be of;
- * - an array of one element: the minimum length that the value should be of. For example, `[8]`.
- * This will overwrite [[min]].
- * - an array of two elements: the minimum and maximum lengths that the value should be of.
- * For example, `[8, 128]`. This will overwrite both [[min]] and [[max]].
- */
- public $length;
- /**
- * @var integer maximum length. If not set, it means no maximum length limit.
- */
- public $max;
- /**
- * @var integer minimum length. If not set, it means no minimum length limit.
- */
- public $min;
- /**
- * @var string user-defined error message used when the value is not a string
- */
- public $message;
- /**
- * @var string user-defined error message used when the length of the value is smaller than [[min]].
- */
- public $tooShort;
- /**
- * @var string user-defined error message used when the length of the value is greater than [[max]].
- */
- public $tooLong;
- /**
- * @var string user-defined error message used when the length of the value is not equal to [[length]].
- */
- public $notEqual;
- /**
- * @var string the encoding of the string value to be validated (e.g. 'UTF-8').
- * If this property is not set, [[\yii\base\Application::charset]] will be used.
- */
- public $encoding;
-
-
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if (is_array($this->length)) {
- if (isset($this->length[0])) {
- $this->min = $this->length[0];
- }
- if (isset($this->length[1])) {
- $this->max = $this->length[1];
- }
- $this->length = null;
- }
- if ($this->encoding === null) {
- $this->encoding = Yii::$app->charset;
- }
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} must be a string.');
- }
- if ($this->min !== null && $this->tooShort === null) {
- $this->tooShort = Yii::t('yii', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.');
- }
- if ($this->max !== null && $this->tooLong === null) {
- $this->tooLong = Yii::t('yii', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.');
- }
- if ($this->length !== null && $this->notEqual === null) {
- $this->notEqual = Yii::t('yii', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.');
- }
- }
-
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $value = $object->$attribute;
-
- if (!is_string($value)) {
- $this->addError($object, $attribute, $this->message);
- return;
- }
-
- $length = mb_strlen($value, $this->encoding);
-
- if ($this->min !== null && $length < $this->min) {
- $this->addError($object, $attribute, $this->tooShort, ['min' => $this->min]);
- }
- if ($this->max !== null && $length > $this->max) {
- $this->addError($object, $attribute, $this->tooLong, ['max' => $this->max]);
- }
- if ($this->length !== null && $length !== $this->length) {
- $this->addError($object, $attribute, $this->notEqual, ['length' => $this->length]);
- }
- }
-
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- if (!is_string($value)) {
- return [$this->message, []];
- }
-
- $length = mb_strlen($value, $this->encoding);
-
- if ($this->min !== null && $length < $this->min) {
- return [$this->tooShort, ['min' => $this->min]];
- }
- if ($this->max !== null && $length > $this->max) {
- return [$this->tooLong, ['max' => $this->max]];
- }
- if ($this->length !== null && $length !== $this->length) {
- return [$this->notEqual, ['length' => $this->length]];
- }
-
- return null;
- }
-
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- $label = $object->getAttributeLabel($attribute);
-
- $options = [
- 'message' => Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $label,
- ], Yii::$app->language),
- ];
-
- if ($this->min !== null) {
- $options['min'] = $this->min;
- $options['tooShort'] = Yii::$app->getI18n()->format($this->tooShort, [
- 'attribute' => $label,
- 'min' => $this->min,
- ], Yii::$app->language);
- }
- if ($this->max !== null) {
- $options['max'] = $this->max;
- $options['tooLong'] = Yii::$app->getI18n()->format($this->tooLong, [
- 'attribute' => $label,
- 'max' => $this->max,
- ], Yii::$app->language);
- }
- if ($this->length !== null) {
- $options['is'] = $this->length;
- $options['notEqual'] = Yii::$app->getI18n()->format($this->notEqual, [
- 'attribute' => $label,
- 'length' => $this->length,
- ], Yii::$app->language);
- }
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
-
- ValidationAsset::register($view);
- return 'yii.validation.string(value, messages, ' . json_encode($options) . ');';
- }
+ /**
+ * @var integer|array specifies the length limit of the value to be validated.
+ * This can be specified in one of the following forms:
+ *
+ * - an integer: the exact length that the value should be of;
+ * - an array of one element: the minimum length that the value should be of. For example, `[8]`.
+ * This will overwrite [[min]].
+ * - an array of two elements: the minimum and maximum lengths that the value should be of.
+ * For example, `[8, 128]`. This will overwrite both [[min]] and [[max]].
+ */
+ public $length;
+ /**
+ * @var integer maximum length. If not set, it means no maximum length limit.
+ */
+ public $max;
+ /**
+ * @var integer minimum length. If not set, it means no minimum length limit.
+ */
+ public $min;
+ /**
+ * @var string user-defined error message used when the value is not a string
+ */
+ public $message;
+ /**
+ * @var string user-defined error message used when the length of the value is smaller than [[min]].
+ */
+ public $tooShort;
+ /**
+ * @var string user-defined error message used when the length of the value is greater than [[max]].
+ */
+ public $tooLong;
+ /**
+ * @var string user-defined error message used when the length of the value is not equal to [[length]].
+ */
+ public $notEqual;
+ /**
+ * @var string the encoding of the string value to be validated (e.g. 'UTF-8').
+ * If this property is not set, [[\yii\base\Application::charset]] will be used.
+ */
+ public $encoding;
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_array($this->length)) {
+ if (isset($this->length[0])) {
+ $this->min = $this->length[0];
+ }
+ if (isset($this->length[1])) {
+ $this->max = $this->length[1];
+ }
+ $this->length = null;
+ }
+ if ($this->encoding === null) {
+ $this->encoding = Yii::$app->charset;
+ }
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} must be a string.');
+ }
+ if ($this->min !== null && $this->tooShort === null) {
+ $this->tooShort = Yii::t('yii', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.');
+ }
+ if ($this->max !== null && $this->tooLong === null) {
+ $this->tooLong = Yii::t('yii', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.');
+ }
+ if ($this->length !== null && $this->notEqual === null) {
+ $this->notEqual = Yii::t('yii', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.');
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $value = $object->$attribute;
+
+ if (!is_string($value)) {
+ $this->addError($object, $attribute, $this->message);
+
+ return;
+ }
+
+ $length = mb_strlen($value, $this->encoding);
+
+ if ($this->min !== null && $length < $this->min) {
+ $this->addError($object, $attribute, $this->tooShort, ['min' => $this->min]);
+ }
+ if ($this->max !== null && $length > $this->max) {
+ $this->addError($object, $attribute, $this->tooLong, ['max' => $this->max]);
+ }
+ if ($this->length !== null && $length !== $this->length) {
+ $this->addError($object, $attribute, $this->notEqual, ['length' => $this->length]);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ if (!is_string($value)) {
+ return [$this->message, []];
+ }
+
+ $length = mb_strlen($value, $this->encoding);
+
+ if ($this->min !== null && $length < $this->min) {
+ return [$this->tooShort, ['min' => $this->min]];
+ }
+ if ($this->max !== null && $length > $this->max) {
+ return [$this->tooLong, ['max' => $this->max]];
+ }
+ if ($this->length !== null && $length !== $this->length) {
+ return [$this->notEqual, ['length' => $this->length]];
+ }
+
+ return null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $label = $object->getAttributeLabel($attribute);
+
+ $options = [
+ 'message' => Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $label,
+ ], Yii::$app->language),
+ ];
+
+ if ($this->min !== null) {
+ $options['min'] = $this->min;
+ $options['tooShort'] = Yii::$app->getI18n()->format($this->tooShort, [
+ 'attribute' => $label,
+ 'min' => $this->min,
+ ], Yii::$app->language);
+ }
+ if ($this->max !== null) {
+ $options['max'] = $this->max;
+ $options['tooLong'] = Yii::$app->getI18n()->format($this->tooLong, [
+ 'attribute' => $label,
+ 'max' => $this->max,
+ ], Yii::$app->language);
+ }
+ if ($this->length !== null) {
+ $options['is'] = $this->length;
+ $options['notEqual'] = Yii::$app->getI18n()->format($this->notEqual, [
+ 'attribute' => $label,
+ 'length' => $this->length,
+ ], Yii::$app->language);
+ }
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+
+ ValidationAsset::register($view);
+
+ return 'yii.validation.string(value, messages, ' . json_encode($options) . ');';
+ }
}
diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php
index 0b311d615fa..46b52e897b2 100644
--- a/framework/validators/UniqueValidator.php
+++ b/framework/validators/UniqueValidator.php
@@ -36,101 +36,102 @@
*/
class UniqueValidator extends Validator
{
- /**
- * @var string the name of the ActiveRecord class that should be used to validate the uniqueness
- * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
- * @see targetAttribute
- */
- public $targetClass;
- /**
- * @var string|array the name of the ActiveRecord attribute that should be used to
- * validate the uniqueness of the current attribute value. If not set, it will use the name
- * of the attribute currently being validated. You may use an array to validate the uniqueness
- * of multiple columns at the same time. The array values are the attributes that will be
- * used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
- * If the key and the value are the same, you can just specify the value.
- */
- public $targetAttribute;
- /**
- * @var string|array|\Closure additional filter to be applied to the DB query used to check the uniqueness of the attribute value.
- * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
- * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
- * is the [[\yii\db\Query|Query]] object that you can modify in the function.
- */
- public $filter;
+ /**
+ * @var string the name of the ActiveRecord class that should be used to validate the uniqueness
+ * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
+ * @see targetAttribute
+ */
+ public $targetClass;
+ /**
+ * @var string|array the name of the ActiveRecord attribute that should be used to
+ * validate the uniqueness of the current attribute value. If not set, it will use the name
+ * of the attribute currently being validated. You may use an array to validate the uniqueness
+ * of multiple columns at the same time. The array values are the attributes that will be
+ * used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
+ * If the key and the value are the same, you can just specify the value.
+ */
+ public $targetAttribute;
+ /**
+ * @var string|array|\Closure additional filter to be applied to the DB query used to check the uniqueness of the attribute value.
+ * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
+ * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
+ * is the [[\yii\db\Query|Query]] object that you can modify in the function.
+ */
+ public $filter;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} "{value}" has already been taken.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} "{value}" has already been taken.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- /** @var ActiveRecordInterface $targetClass */
- $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
- $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ /** @var ActiveRecordInterface $targetClass */
+ $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
+ $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
- if (is_array($targetAttribute)) {
- $params = [];
- foreach ($targetAttribute as $k => $v) {
- $params[$v] = is_integer($k) ? $object->$v : $object->$k;
- }
- } else {
- $params = [$targetAttribute => $object->$attribute];
- }
+ if (is_array($targetAttribute)) {
+ $params = [];
+ foreach ($targetAttribute as $k => $v) {
+ $params[$v] = is_integer($k) ? $object->$v : $object->$k;
+ }
+ } else {
+ $params = [$targetAttribute => $object->$attribute];
+ }
- foreach ($params as $value) {
- if (is_array($value)) {
- $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- return;
- }
- }
+ foreach ($params as $value) {
+ if (is_array($value)) {
+ $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
- $query = $targetClass::find();
- $query->where($params);
+ return;
+ }
+ }
- if ($this->filter instanceof \Closure) {
- call_user_func($this->filter, $query);
- } elseif ($this->filter !== null) {
- $query->andWhere($this->filter);
- }
+ $query = $targetClass::find();
+ $query->where($params);
- if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) {
- // if current $object isn't in the database yet then it's OK just to call exists()
- $exists = $query->exists();
- } else {
- // if current $object is in the database already we can't use exists()
- /** @var ActiveRecordInterface[] $objects */
- $objects = $query->limit(2)->all();
- $n = count($objects);
- if ($n === 1) {
- $keys = array_keys($params);
- $pks = $targetClass::primaryKey();
- sort($keys);
- sort($pks);
- if ($keys === $pks) {
- // primary key is modified and not unique
- $exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
- } else {
- // non-primary key, need to exclude the current record based on PK
- $exists = $objects[0]->getPrimaryKey() != $object->getOldPrimaryKey();
- }
- } else {
- $exists = $n > 1;
- }
- }
+ if ($this->filter instanceof \Closure) {
+ call_user_func($this->filter, $query);
+ } elseif ($this->filter !== null) {
+ $query->andWhere($this->filter);
+ }
- if ($exists) {
- $this->addError($object, $attribute, $this->message);
- }
- }
+ if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) {
+ // if current $object isn't in the database yet then it's OK just to call exists()
+ $exists = $query->exists();
+ } else {
+ // if current $object is in the database already we can't use exists()
+ /** @var ActiveRecordInterface[] $objects */
+ $objects = $query->limit(2)->all();
+ $n = count($objects);
+ if ($n === 1) {
+ $keys = array_keys($params);
+ $pks = $targetClass::primaryKey();
+ sort($keys);
+ sort($pks);
+ if ($keys === $pks) {
+ // primary key is modified and not unique
+ $exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
+ } else {
+ // non-primary key, need to exclude the current record based on PK
+ $exists = $objects[0]->getPrimaryKey() != $object->getOldPrimaryKey();
+ }
+ } else {
+ $exists = $n > 1;
+ }
+ }
+
+ if ($exists) {
+ $this->addError($object, $attribute, $this->message);
+ }
+ }
}
diff --git a/framework/validators/UrlValidator.php b/framework/validators/UrlValidator.php
index feaf145abbd..cf31642e543 100644
--- a/framework/validators/UrlValidator.php
+++ b/framework/validators/UrlValidator.php
@@ -23,119 +23,121 @@
*/
class UrlValidator extends Validator
{
- /**
- * @var string the regular expression used to validate the attribute value.
- * The pattern may contain a `{schemes}` token that will be replaced
- * by a regular expression which represents the [[validSchemes]].
- */
- public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i';
- /**
- * @var array list of URI schemes which should be considered valid. By default, http and https
- * are considered to be valid schemes.
- **/
- public $validSchemes = ['http', 'https'];
- /**
- * @var string the default URI scheme. If the input doesn't contain the scheme part, the default
- * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must
- * contain the scheme part.
- **/
- public $defaultScheme;
- /**
- * @var boolean whether validation process should take into account IDN (internationalized
- * domain names). Defaults to false meaning that validation of URLs containing IDN will always
- * fail. Note that in order to use IDN validation you have to install and enable `intl` PHP
- * extension, otherwise an exception would be thrown.
- */
- public $enableIDN = false;
+ /**
+ * @var string the regular expression used to validate the attribute value.
+ * The pattern may contain a `{schemes}` token that will be replaced
+ * by a regular expression which represents the [[validSchemes]].
+ */
+ public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i';
+ /**
+ * @var array list of URI schemes which should be considered valid. By default, http and https
+ * are considered to be valid schemes.
+ **/
+ public $validSchemes = ['http', 'https'];
+ /**
+ * @var string the default URI scheme. If the input doesn't contain the scheme part, the default
+ * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must
+ * contain the scheme part.
+ **/
+ public $defaultScheme;
+ /**
+ * @var boolean whether validation process should take into account IDN (internationalized
+ * domain names). Defaults to false meaning that validation of URLs containing IDN will always
+ * fail. Note that in order to use IDN validation you have to install and enable `intl` PHP
+ * extension, otherwise an exception would be thrown.
+ */
+ public $enableIDN = false;
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- if ($this->enableIDN && !function_exists('idn_to_ascii')) {
- throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
- }
- if ($this->message === null) {
- $this->message = Yii::t('yii', '{attribute} is not a valid URL.');
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->enableIDN && !function_exists('idn_to_ascii')) {
+ throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
+ }
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', '{attribute} is not a valid URL.');
+ }
+ }
- /**
- * @inheritdoc
- */
- public function validateAttribute($object, $attribute)
- {
- $value = $object->$attribute;
- $result = $this->validateValue($value);
- if (!empty($result)) {
- $this->addError($object, $attribute, $result[0], $result[1]);
- } elseif ($this->defaultScheme !== null && strpos($value, '://') === false) {
- $object->$attribute = $this->defaultScheme . '://' . $value;
- }
- }
+ /**
+ * @inheritdoc
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $value = $object->$attribute;
+ $result = $this->validateValue($value);
+ if (!empty($result)) {
+ $this->addError($object, $attribute, $result[0], $result[1]);
+ } elseif ($this->defaultScheme !== null && strpos($value, '://') === false) {
+ $object->$attribute = $this->defaultScheme . '://' . $value;
+ }
+ }
- /**
- * @inheritdoc
- */
- protected function validateValue($value)
- {
- // make sure the length is limited to avoid DOS attacks
- if (is_string($value) && strlen($value) < 2000) {
- if ($this->defaultScheme !== null && strpos($value, '://') === false) {
- $value = $this->defaultScheme . '://' . $value;
- }
+ /**
+ * @inheritdoc
+ */
+ protected function validateValue($value)
+ {
+ // make sure the length is limited to avoid DOS attacks
+ if (is_string($value) && strlen($value) < 2000) {
+ if ($this->defaultScheme !== null && strpos($value, '://') === false) {
+ $value = $this->defaultScheme . '://' . $value;
+ }
- if (strpos($this->pattern, '{schemes}') !== false) {
- $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
- } else {
- $pattern = $this->pattern;
- }
+ if (strpos($this->pattern, '{schemes}') !== false) {
+ $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
+ } else {
+ $pattern = $this->pattern;
+ }
- if ($this->enableIDN) {
- $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) {
- return '://' . idn_to_ascii($matches[1]);
- }, $value);
- }
+ if ($this->enableIDN) {
+ $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) {
+ return '://' . idn_to_ascii($matches[1]);
+ }, $value);
+ }
- if (preg_match($pattern, $value)) {
- return null;
- }
- }
- return [$this->message, []];
- }
+ if (preg_match($pattern, $value)) {
+ return null;
+ }
+ }
- /**
- * @inheritdoc
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- if (strpos($this->pattern, '{schemes}') !== false) {
- $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
- } else {
- $pattern = $this->pattern;
- }
+ return [$this->message, []];
+ }
- $options = [
- 'pattern' => new JsExpression($pattern),
- 'message' => Yii::$app->getI18n()->format($this->message, [
- 'attribute' => $object->getAttributeLabel($attribute),
- ], Yii::$app->language),
- 'enableIDN' => (boolean)$this->enableIDN,
- ];
- if ($this->skipOnEmpty) {
- $options['skipOnEmpty'] = 1;
- }
- if ($this->defaultScheme !== null) {
- $options['defaultScheme'] = $this->defaultScheme;
- }
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ if (strpos($this->pattern, '{schemes}') !== false) {
+ $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
+ } else {
+ $pattern = $this->pattern;
+ }
- ValidationAsset::register($view);
- if ($this->enableIDN) {
- PunycodeAsset::register($view);
- }
- return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');';
- }
+ $options = [
+ 'pattern' => new JsExpression($pattern),
+ 'message' => Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $object->getAttributeLabel($attribute),
+ ], Yii::$app->language),
+ 'enableIDN' => (boolean) $this->enableIDN,
+ ];
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+ if ($this->defaultScheme !== null) {
+ $options['defaultScheme'] = $this->defaultScheme;
+ }
+
+ ValidationAsset::register($view);
+ if ($this->enableIDN) {
+ PunycodeAsset::register($view);
+ }
+
+ return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');';
+ }
}
diff --git a/framework/validators/ValidationAsset.php b/framework/validators/ValidationAsset.php
index e9bb79d305c..a25acffd625 100644
--- a/framework/validators/ValidationAsset.php
+++ b/framework/validators/ValidationAsset.php
@@ -17,11 +17,11 @@
*/
class ValidationAsset extends AssetBundle
{
- public $sourcePath = '@yii/assets';
- public $js = [
- 'yii.validation.js',
- ];
- public $depends = [
- 'yii\web\YiiAsset',
- ];
+ public $sourcePath = '@yii/assets';
+ public $js = [
+ 'yii.validation.js',
+ ];
+ public $depends = [
+ 'yii\web\YiiAsset',
+ ];
}
diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php
index fac370650ba..5a3b4703919 100644
--- a/framework/validators/Validator.php
+++ b/framework/validators/Validator.php
@@ -47,266 +47,266 @@
*/
class Validator extends Component
{
- /**
- * @var array list of built-in validators (name => class or configuration)
- */
- public static $builtInValidators = [
- 'boolean' => 'yii\validators\BooleanValidator',
- 'captcha' => 'yii\captcha\CaptchaValidator',
- 'compare' => 'yii\validators\CompareValidator',
- 'date' => 'yii\validators\DateValidator',
- 'default' => 'yii\validators\DefaultValueValidator',
- 'double' => 'yii\validators\NumberValidator',
- 'email' => 'yii\validators\EmailValidator',
- 'exist' => 'yii\validators\ExistValidator',
- 'file' => 'yii\validators\FileValidator',
- 'filter' => 'yii\validators\FilterValidator',
- 'image' => 'yii\validators\ImageValidator',
- 'in' => 'yii\validators\RangeValidator',
- 'integer' => [
- 'class' => 'yii\validators\NumberValidator',
- 'integerOnly' => true,
- ],
- 'match' => 'yii\validators\RegularExpressionValidator',
- 'number' => 'yii\validators\NumberValidator',
- 'required' => 'yii\validators\RequiredValidator',
- 'safe' => 'yii\validators\SafeValidator',
- 'string' => 'yii\validators\StringValidator',
- 'trim' => [
- 'class' => 'yii\validators\FilterValidator',
- 'filter' => 'trim',
- ],
- 'unique' => 'yii\validators\UniqueValidator',
- 'url' => 'yii\validators\UrlValidator',
- ];
+ /**
+ * @var array list of built-in validators (name => class or configuration)
+ */
+ public static $builtInValidators = [
+ 'boolean' => 'yii\validators\BooleanValidator',
+ 'captcha' => 'yii\captcha\CaptchaValidator',
+ 'compare' => 'yii\validators\CompareValidator',
+ 'date' => 'yii\validators\DateValidator',
+ 'default' => 'yii\validators\DefaultValueValidator',
+ 'double' => 'yii\validators\NumberValidator',
+ 'email' => 'yii\validators\EmailValidator',
+ 'exist' => 'yii\validators\ExistValidator',
+ 'file' => 'yii\validators\FileValidator',
+ 'filter' => 'yii\validators\FilterValidator',
+ 'image' => 'yii\validators\ImageValidator',
+ 'in' => 'yii\validators\RangeValidator',
+ 'integer' => [
+ 'class' => 'yii\validators\NumberValidator',
+ 'integerOnly' => true,
+ ],
+ 'match' => 'yii\validators\RegularExpressionValidator',
+ 'number' => 'yii\validators\NumberValidator',
+ 'required' => 'yii\validators\RequiredValidator',
+ 'safe' => 'yii\validators\SafeValidator',
+ 'string' => 'yii\validators\StringValidator',
+ 'trim' => [
+ 'class' => 'yii\validators\FilterValidator',
+ 'filter' => 'trim',
+ ],
+ 'unique' => 'yii\validators\UniqueValidator',
+ 'url' => 'yii\validators\UrlValidator',
+ ];
- /**
- * @var array|string attributes to be validated by this validator. For multiple attributes,
- * please specify them as an array; for single attribute, you may use either a string or an array.
- */
- public $attributes = [];
- /**
- * @var string the user-defined error message. It may contain the following placeholders which
- * will be replaced accordingly by the validator:
- *
- * - `{attribute}`: the label of the attribute being validated
- * - `{value}`: the value of the attribute being validated
- */
- public $message;
- /**
- * @var array|string scenarios that the validator can be applied to. For multiple scenarios,
- * please specify them as an array; for single scenario, you may use either a string or an array.
- */
- public $on = [];
- /**
- * @var array|string scenarios that the validator should not be applied to. For multiple scenarios,
- * please specify them as an array; for single scenario, you may use either a string or an array.
- */
- public $except = [];
- /**
- * @var boolean whether this validation rule should be skipped if the attribute being validated
- * already has some validation error according to some previous rules. Defaults to true.
- */
- public $skipOnError = true;
- /**
- * @var boolean whether this validation rule should be skipped if the attribute value
- * is null or an empty string.
- */
- public $skipOnEmpty = true;
- /**
- * @var boolean whether to enable client-side validation for this validator.
- * The actual client-side validation is done via the JavaScript code returned
- * by [[clientValidateAttribute()]]. If that method returns null, even if this property
- * is true, no client-side validation will be done by this validator.
- */
- public $enableClientValidation = true;
+ /**
+ * @var array|string attributes to be validated by this validator. For multiple attributes,
+ * please specify them as an array; for single attribute, you may use either a string or an array.
+ */
+ public $attributes = [];
+ /**
+ * @var string the user-defined error message. It may contain the following placeholders which
+ * will be replaced accordingly by the validator:
+ *
+ * - `{attribute}`: the label of the attribute being validated
+ * - `{value}`: the value of the attribute being validated
+ */
+ public $message;
+ /**
+ * @var array|string scenarios that the validator can be applied to. For multiple scenarios,
+ * please specify them as an array; for single scenario, you may use either a string or an array.
+ */
+ public $on = [];
+ /**
+ * @var array|string scenarios that the validator should not be applied to. For multiple scenarios,
+ * please specify them as an array; for single scenario, you may use either a string or an array.
+ */
+ public $except = [];
+ /**
+ * @var boolean whether this validation rule should be skipped if the attribute being validated
+ * already has some validation error according to some previous rules. Defaults to true.
+ */
+ public $skipOnError = true;
+ /**
+ * @var boolean whether this validation rule should be skipped if the attribute value
+ * is null or an empty string.
+ */
+ public $skipOnEmpty = true;
+ /**
+ * @var boolean whether to enable client-side validation for this validator.
+ * The actual client-side validation is done via the JavaScript code returned
+ * by [[clientValidateAttribute()]]. If that method returns null, even if this property
+ * is true, no client-side validation will be done by this validator.
+ */
+ public $enableClientValidation = true;
+ /**
+ * Creates a validator object.
+ * @param mixed $type the validator type. This can be a built-in validator name,
+ * a method name of the model class, an anonymous function, or a validator class name.
+ * @param \yii\base\Model $object the data object to be validated.
+ * @param array|string $attributes list of attributes to be validated. This can be either an array of
+ * the attribute names or a string of comma-separated attribute names.
+ * @param array $params initial values to be applied to the validator properties
+ * @return Validator the validator
+ */
+ public static function createValidator($type, $object, $attributes, $params = [])
+ {
+ $params['attributes'] = $attributes;
- /**
- * Creates a validator object.
- * @param mixed $type the validator type. This can be a built-in validator name,
- * a method name of the model class, an anonymous function, or a validator class name.
- * @param \yii\base\Model $object the data object to be validated.
- * @param array|string $attributes list of attributes to be validated. This can be either an array of
- * the attribute names or a string of comma-separated attribute names.
- * @param array $params initial values to be applied to the validator properties
- * @return Validator the validator
- */
- public static function createValidator($type, $object, $attributes, $params = [])
- {
- $params['attributes'] = $attributes;
+ if ($type instanceof \Closure || $object->hasMethod($type)) {
+ // method-based validator
+ $params['class'] = __NAMESPACE__ . '\InlineValidator';
+ $params['method'] = $type;
+ } else {
+ if (isset(static::$builtInValidators[$type])) {
+ $type = static::$builtInValidators[$type];
+ }
+ if (is_array($type)) {
+ foreach ($type as $name => $value) {
+ $params[$name] = $value;
+ }
+ } else {
+ $params['class'] = $type;
+ }
+ }
- if ($type instanceof \Closure || $object->hasMethod($type)) {
- // method-based validator
- $params['class'] = __NAMESPACE__ . '\InlineValidator';
- $params['method'] = $type;
- } else {
- if (isset(static::$builtInValidators[$type])) {
- $type = static::$builtInValidators[$type];
- }
- if (is_array($type)) {
- foreach ($type as $name => $value) {
- $params[$name] = $value;
- }
- } else {
- $params['class'] = $type;
- }
- }
+ return Yii::createObject($params);
+ }
- return Yii::createObject($params);
- }
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ $this->attributes = (array) $this->attributes;
+ $this->on = (array) $this->on;
+ $this->except = (array) $this->except;
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- $this->attributes = (array)$this->attributes;
- $this->on = (array)$this->on;
- $this->except = (array)$this->except;
- }
+ /**
+ * Validates the specified object.
+ * @param \yii\base\Model $object the data object being validated
+ * @param array|null $attributes the list of attributes to be validated.
+ * Note that if an attribute is not associated with the validator,
+ * it will be ignored.
+ * If this parameter is null, every attribute listed in [[attributes]] will be validated.
+ */
+ public function validateAttributes($object, $attributes = null)
+ {
+ if (is_array($attributes)) {
+ $attributes = array_intersect($this->attributes, $attributes);
+ } else {
+ $attributes = $this->attributes;
+ }
+ foreach ($attributes as $attribute) {
+ $skip = $this->skipOnError && $object->hasErrors($attribute)
+ || $this->skipOnEmpty && $this->isEmpty($object->$attribute);
+ if (!$skip) {
+ $this->validateAttribute($object, $attribute);
+ }
+ }
+ }
- /**
- * Validates the specified object.
- * @param \yii\base\Model $object the data object being validated
- * @param array|null $attributes the list of attributes to be validated.
- * Note that if an attribute is not associated with the validator,
- * it will be ignored.
- * If this parameter is null, every attribute listed in [[attributes]] will be validated.
- */
- public function validateAttributes($object, $attributes = null)
- {
- if (is_array($attributes)) {
- $attributes = array_intersect($this->attributes, $attributes);
- } else {
- $attributes = $this->attributes;
- }
- foreach ($attributes as $attribute) {
- $skip = $this->skipOnError && $object->hasErrors($attribute)
- || $this->skipOnEmpty && $this->isEmpty($object->$attribute);
- if (!$skip) {
- $this->validateAttribute($object, $attribute);
- }
- }
- }
+ /**
+ * Validates a single attribute.
+ * Child classes must implement this method to provide the actual validation logic.
+ * @param \yii\base\Model $object the data object to be validated
+ * @param string $attribute the name of the attribute to be validated.
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $result = $this->validateValue($object->$attribute);
+ if (!empty($result)) {
+ $this->addError($object, $attribute, $result[0], $result[1]);
+ }
+ }
- /**
- * Validates a single attribute.
- * Child classes must implement this method to provide the actual validation logic.
- * @param \yii\base\Model $object the data object to be validated
- * @param string $attribute the name of the attribute to be validated.
- */
- public function validateAttribute($object, $attribute)
- {
- $result = $this->validateValue($object->$attribute);
- if (!empty($result)) {
- $this->addError($object, $attribute, $result[0], $result[1]);
- }
- }
+ /**
+ * Validates a given value.
+ * You may use this method to validate a value out of the context of a data model.
+ * @param mixed $value the data value to be validated.
+ * @param string $error the error message to be returned, if the validation fails.
+ * @return boolean whether the data is valid.
+ */
+ public function validate($value, &$error = null)
+ {
+ $result = $this->validateValue($value);
+ if (empty($result)) {
+ return true;
+ } else {
+ list($message, $params) = $result;
+ $params['attribute'] = Yii::t('yii', 'the input value');
+ $params['value'] = is_array($value) ? 'array()' : $value;
+ $error = Yii::$app->getI18n()->format($message, $params, Yii::$app->language);
- /**
- * Validates a given value.
- * You may use this method to validate a value out of the context of a data model.
- * @param mixed $value the data value to be validated.
- * @param string $error the error message to be returned, if the validation fails.
- * @return boolean whether the data is valid.
- */
- public function validate($value, &$error = null)
- {
- $result = $this->validateValue($value);
- if (empty($result)) {
- return true;
- } else {
- list($message, $params) = $result;
- $params['attribute'] = Yii::t('yii', 'the input value');
- $params['value'] = is_array($value) ? 'array()' : $value;
- $error = Yii::$app->getI18n()->format($message, $params, Yii::$app->language);
- return false;
- }
- }
+ return false;
+ }
+ }
- /**
- * Validates a value.
- * A validator class can implement this method to support data validation out of the context of a data model.
- * @param mixed $value the data value to be validated.
- * @return array|null the error message and the parameters to be inserted into the error message.
- * Null should be returned if the data is valid.
- * @throws NotSupportedException if the validator does not supporting data validation without a model
- */
- protected function validateValue($value)
- {
- throw new NotSupportedException(get_class($this) . ' does not support validateValue().');
- }
+ /**
+ * Validates a value.
+ * A validator class can implement this method to support data validation out of the context of a data model.
+ * @param mixed $value the data value to be validated.
+ * @return array|null the error message and the parameters to be inserted into the error message.
+ * Null should be returned if the data is valid.
+ * @throws NotSupportedException if the validator does not supporting data validation without a model
+ */
+ protected function validateValue($value)
+ {
+ throw new NotSupportedException(get_class($this) . ' does not support validateValue().');
+ }
- /**
- * Returns the JavaScript needed for performing client-side validation.
- *
- * You may override this method to return the JavaScript validation code if
- * the validator can support client-side validation.
- *
- * The following JavaScript variables are predefined and can be used in the validation code:
- *
- * - `attribute`: the name of the attribute being validated.
- * - `value`: the value being validated.
- * - `messages`: an array used to hold the validation error messages for the attribute.
- *
- * @param \yii\base\Model $object the data object being validated
- * @param string $attribute the name of the attribute to be validated.
- * @param \yii\web\View $view the view object that is going to be used to render views or view files
- * containing a model form with this validator applied.
- * @return string the client-side validation script. Null if the validator does not support
- * client-side validation.
- * @see \yii\widgets\ActiveForm::enableClientValidation
- */
- public function clientValidateAttribute($object, $attribute, $view)
- {
- return null;
- }
+ /**
+ * Returns the JavaScript needed for performing client-side validation.
+ *
+ * You may override this method to return the JavaScript validation code if
+ * the validator can support client-side validation.
+ *
+ * The following JavaScript variables are predefined and can be used in the validation code:
+ *
+ * - `attribute`: the name of the attribute being validated.
+ * - `value`: the value being validated.
+ * - `messages`: an array used to hold the validation error messages for the attribute.
+ *
+ * @param \yii\base\Model $object the data object being validated
+ * @param string $attribute the name of the attribute to be validated.
+ * @param \yii\web\View $view the view object that is going to be used to render views or view files
+ * containing a model form with this validator applied.
+ * @return string the client-side validation script. Null if the validator does not support
+ * client-side validation.
+ * @see \yii\widgets\ActiveForm::enableClientValidation
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ return null;
+ }
- /**
- * Returns a value indicating whether the validator is active for the given scenario and attribute.
- *
- * A validator is active if
- *
- * - the validator's `on` property is empty, or
- * - the validator's `on` property contains the specified scenario
- *
- * @param string $scenario scenario name
- * @return boolean whether the validator applies to the specified scenario.
- */
- public function isActive($scenario)
- {
- return !in_array($scenario, $this->except, true) && (empty($this->on) || in_array($scenario, $this->on, true));
- }
+ /**
+ * Returns a value indicating whether the validator is active for the given scenario and attribute.
+ *
+ * A validator is active if
+ *
+ * - the validator's `on` property is empty, or
+ * - the validator's `on` property contains the specified scenario
+ *
+ * @param string $scenario scenario name
+ * @return boolean whether the validator applies to the specified scenario.
+ */
+ public function isActive($scenario)
+ {
+ return !in_array($scenario, $this->except, true) && (empty($this->on) || in_array($scenario, $this->on, true));
+ }
- /**
- * Adds an error about the specified attribute to the model object.
- * This is a helper method that performs message selection and internationalization.
- * @param \yii\base\Model $object the data object being validated
- * @param string $attribute the attribute being validated
- * @param string $message the error message
- * @param array $params values for the placeholders in the error message
- */
- public function addError($object, $attribute, $message, $params = [])
- {
- $value = $object->$attribute;
- $params['attribute'] = $object->getAttributeLabel($attribute);
- $params['value'] = is_array($value) ? 'array()' : $value;
- $object->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language));
- }
+ /**
+ * Adds an error about the specified attribute to the model object.
+ * This is a helper method that performs message selection and internationalization.
+ * @param \yii\base\Model $object the data object being validated
+ * @param string $attribute the attribute being validated
+ * @param string $message the error message
+ * @param array $params values for the placeholders in the error message
+ */
+ public function addError($object, $attribute, $message, $params = [])
+ {
+ $value = $object->$attribute;
+ $params['attribute'] = $object->getAttributeLabel($attribute);
+ $params['value'] = is_array($value) ? 'array()' : $value;
+ $object->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language));
+ }
- /**
- * Checks if the given value is empty.
- * A value is considered empty if it is null, an empty array, or the trimmed result is an empty string.
- * Note that this method is different from PHP empty(). It will return false when the value is 0.
- * @param mixed $value the value to be checked
- * @param boolean $trim whether to perform trimming before checking if the string is empty. Defaults to false.
- * @return boolean whether the value is empty
- */
- public function isEmpty($value, $trim = false)
- {
- return $value === null || $value === [] || $value === ''
- || $trim && is_scalar($value) && trim($value) === '';
- }
+ /**
+ * Checks if the given value is empty.
+ * A value is considered empty if it is null, an empty array, or the trimmed result is an empty string.
+ * Note that this method is different from PHP empty(). It will return false when the value is 0.
+ * @param mixed $value the value to be checked
+ * @param boolean $trim whether to perform trimming before checking if the string is empty. Defaults to false.
+ * @return boolean whether the value is empty
+ */
+ public function isEmpty($value, $trim = false)
+ {
+ return $value === null || $value === [] || $value === ''
+ || $trim && is_scalar($value) && trim($value) === '';
+ }
}
diff --git a/framework/views/errorHandler/callStackItem.php b/framework/views/errorHandler/callStackItem.php
index 566e1d317dc..eda747c82aa 100644
--- a/framework/views/errorHandler/callStackItem.php
+++ b/framework/views/errorHandler/callStackItem.php
@@ -12,34 +12,34 @@
*/
?>
diff --git a/framework/views/messageConfig.php b/framework/views/messageConfig.php
index f926c829f60..6b4e8c9b7ec 100644
--- a/framework/views/messageConfig.php
+++ b/framework/views/messageConfig.php
@@ -1,52 +1,52 @@
__DIR__,
- // string, required, root directory containing message translations.
- 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
- // array, required, list of language codes that the extracted messages
- // should be translated to. For example, ['zh-CN', 'de'].
- 'languages' => ['de'],
- // string, the name of the function for translating messages.
- // Defaults to 'Yii::t'. This is used as a mark to find the messages to be
- // translated. You may use a string for single function name or an array for
- // multiple function names.
- 'translator' => 'Yii::t',
- // boolean, whether to sort messages by keys when merging new messages
- // with the existing ones. Defaults to false, which means the new (untranslated)
- // messages will be separated from the old (translated) ones.
- 'sort' => false,
- // boolean, whether the message file should be overwritten with the merged messages
- 'overwrite' => true,
- // boolean, whether to remove messages that no longer appear in the source code.
- // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks.
- 'removeUnused' => false,
- // array, list of patterns that specify which files/directories should NOT be processed.
- // If empty or not set, all files/directories will be processed.
- // A path matches a pattern if it contains the pattern string at its end. For example,
- // '/a/b' will match all files and directories ending with '/a/b';
- // the '*.svn' will match all files and directories whose name ends with '.svn'.
- // and the '.svn' will match all files and directories named exactly '.svn'.
- // Note, the '/' characters in a pattern matches both '/' and '\'.
- // See helpers/FileHelper::findFiles() description for more details on pattern matching rules.
- 'only' => ['*.php'],
- // array, list of patterns that specify which files (not directories) should be processed.
- // If empty or not set, all files will be processed.
- // Please refer to "except" for details about the patterns.
- // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
- 'except' => [
- '.svn',
- '.git',
- '.gitignore',
- '.gitkeep',
- '.hgignore',
- '.hgkeep',
- '/messages',
- ],
- // Generated file format. Can be either "php", "po" or "db".
- 'format' => 'php',
- // When format is "db", you may specify the following two options
- //'db' => 'db',
- //'sourceMessageTable' => '{{%source_message}}',
+ // string, required, root directory of all source files
+ 'sourcePath' => __DIR__,
+ // string, required, root directory containing message translations.
+ 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
+ // array, required, list of language codes that the extracted messages
+ // should be translated to. For example, ['zh-CN', 'de'].
+ 'languages' => ['de'],
+ // string, the name of the function for translating messages.
+ // Defaults to 'Yii::t'. This is used as a mark to find the messages to be
+ // translated. You may use a string for single function name or an array for
+ // multiple function names.
+ 'translator' => 'Yii::t',
+ // boolean, whether to sort messages by keys when merging new messages
+ // with the existing ones. Defaults to false, which means the new (untranslated)
+ // messages will be separated from the old (translated) ones.
+ 'sort' => false,
+ // boolean, whether the message file should be overwritten with the merged messages
+ 'overwrite' => true,
+ // boolean, whether to remove messages that no longer appear in the source code.
+ // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks.
+ 'removeUnused' => false,
+ // array, list of patterns that specify which files/directories should NOT be processed.
+ // If empty or not set, all files/directories will be processed.
+ // A path matches a pattern if it contains the pattern string at its end. For example,
+ // '/a/b' will match all files and directories ending with '/a/b';
+ // the '*.svn' will match all files and directories whose name ends with '.svn'.
+ // and the '.svn' will match all files and directories named exactly '.svn'.
+ // Note, the '/' characters in a pattern matches both '/' and '\'.
+ // See helpers/FileHelper::findFiles() description for more details on pattern matching rules.
+ 'only' => ['*.php'],
+ // array, list of patterns that specify which files (not directories) should be processed.
+ // If empty or not set, all files will be processed.
+ // Please refer to "except" for details about the patterns.
+ // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
+ 'except' => [
+ '.svn',
+ '.git',
+ '.gitignore',
+ '.gitkeep',
+ '.hgignore',
+ '.hgkeep',
+ '/messages',
+ ],
+ // Generated file format. Can be either "php", "po" or "db".
+ 'format' => 'php',
+ // When format is "db", you may specify the following two options
+ //'db' => 'db',
+ //'sourceMessageTable' => '{{%source_message}}',
];
diff --git a/framework/views/migration.php b/framework/views/migration.php
index f6add4d151d..c0e6a9810e9 100644
--- a/framework/views/migration.php
+++ b/framework/views/migration.php
@@ -8,18 +8,17 @@
echo "
-use yii\db\Schema;
-
class = $className ?> extends \yii\db\Migration
{
- public function up()
- {
+ public function up()
+ {
+
+ }
- }
+ public function down()
+ {
+ echo "= $className ?> cannot be reverted.\n";
- public function down()
- {
- echo "= $className ?> cannot be reverted.\n";
- return false;
- }
+ return false;
+ }
}
diff --git a/framework/web/AccessControl.php b/framework/web/AccessControl.php
index c4b38e4dece..89441a7e37d 100644
--- a/framework/web/AccessControl.php
+++ b/framework/web/AccessControl.php
@@ -53,91 +53,93 @@
*/
class AccessControl extends ActionFilter
{
- /**
- * @var callable a callback that will be called if the access should be denied
- * to the current user. If not set, [[denyAccess()]] will be called.
- *
- * The signature of the callback should be as follows:
- *
- * ~~~
- * function ($rule, $action)
- * ~~~
- *
- * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
- */
- public $denyCallback;
- /**
- * @var array the default configuration of access rules. Individual rule configurations
- * specified via [[rules]] will take precedence when the same property of the rule is configured.
- */
- public $ruleConfig = ['class' => 'yii\web\AccessRule'];
- /**
- * @var array a list of access rule objects or configuration arrays for creating the rule objects.
- * If a rule is specified via a configuration array, it will be merged with [[ruleConfig]] first
- * before it is used for creating the rule object.
- * @see ruleConfig
- */
- public $rules = [];
+ /**
+ * @var callable a callback that will be called if the access should be denied
+ * to the current user. If not set, [[denyAccess()]] will be called.
+ *
+ * The signature of the callback should be as follows:
+ *
+ * ~~~
+ * function ($rule, $action)
+ * ~~~
+ *
+ * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
+ */
+ public $denyCallback;
+ /**
+ * @var array the default configuration of access rules. Individual rule configurations
+ * specified via [[rules]] will take precedence when the same property of the rule is configured.
+ */
+ public $ruleConfig = ['class' => 'yii\web\AccessRule'];
+ /**
+ * @var array a list of access rule objects or configuration arrays for creating the rule objects.
+ * If a rule is specified via a configuration array, it will be merged with [[ruleConfig]] first
+ * before it is used for creating the rule object.
+ * @see ruleConfig
+ */
+ public $rules = [];
- /**
- * Initializes the [[rules]] array by instantiating rule objects from configurations.
- */
- public function init()
- {
- parent::init();
- foreach ($this->rules as $i => $rule) {
- if (is_array($rule)) {
- $this->rules[$i] = Yii::createObject(array_merge($this->ruleConfig, $rule));
- }
- }
- }
+ /**
+ * Initializes the [[rules]] array by instantiating rule objects from configurations.
+ */
+ public function init()
+ {
+ parent::init();
+ foreach ($this->rules as $i => $rule) {
+ if (is_array($rule)) {
+ $this->rules[$i] = Yii::createObject(array_merge($this->ruleConfig, $rule));
+ }
+ }
+ }
- /**
- * This method is invoked right before an action is to be executed (after all possible filters.)
- * You may override this method to do last-minute preparation for the action.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- $user = Yii::$app->getUser();
- $request = Yii::$app->getRequest();
- /** @var AccessRule $rule */
- foreach ($this->rules as $rule) {
- if ($allow = $rule->allows($action, $user, $request)) {
- return true;
- } elseif ($allow === false) {
- if (isset($rule->denyCallback)) {
- call_user_func($rule->denyCallback, $rule, $action);
- } elseif (isset($this->denyCallback)) {
- call_user_func($this->denyCallback, $rule, $action);
- } else {
- $this->denyAccess($user);
- }
- return false;
- }
- }
- if (isset($this->denyCallback)) {
- call_user_func($this->denyCallback, $rule, $action);
- } else {
- $this->denyAccess($user);
- }
- return false;
- }
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $user = Yii::$app->getUser();
+ $request = Yii::$app->getRequest();
+ /** @var AccessRule $rule */
+ foreach ($this->rules as $rule) {
+ if ($allow = $rule->allows($action, $user, $request)) {
+ return true;
+ } elseif ($allow === false) {
+ if (isset($rule->denyCallback)) {
+ call_user_func($rule->denyCallback, $rule, $action);
+ } elseif (isset($this->denyCallback)) {
+ call_user_func($this->denyCallback, $rule, $action);
+ } else {
+ $this->denyAccess($user);
+ }
- /**
- * Denies the access of the user.
- * The default implementation will redirect the user to the login page if he is a guest;
- * if the user is already logged, a 403 HTTP exception will be thrown.
- * @param User $user the current user
- * @throws ForbiddenHttpException if the user is already logged in.
- */
- protected function denyAccess($user)
- {
- if ($user->getIsGuest()) {
- $user->loginRequired();
- } else {
- throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
- }
- }
+ return false;
+ }
+ }
+ if (isset($this->denyCallback)) {
+ call_user_func($this->denyCallback, $rule, $action);
+ } else {
+ $this->denyAccess($user);
+ }
+
+ return false;
+ }
+
+ /**
+ * Denies the access of the user.
+ * The default implementation will redirect the user to the login page if he is a guest;
+ * if the user is already logged, a 403 HTTP exception will be thrown.
+ * @param User $user the current user
+ * @throws ForbiddenHttpException if the user is already logged in.
+ */
+ protected function denyAccess($user)
+ {
+ if ($user->getIsGuest()) {
+ $user->loginRequired();
+ } else {
+ throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
+ }
+ }
}
diff --git a/framework/web/AccessRule.php b/framework/web/AccessRule.php
index eb1011646f2..18389adc254 100644
--- a/framework/web/AccessRule.php
+++ b/framework/web/AccessRule.php
@@ -18,169 +18,170 @@
*/
class AccessRule extends Component
{
- /**
- * @var boolean whether this is an 'allow' rule or 'deny' rule.
- */
- public $allow;
- /**
- * @var array list of action IDs that this rule applies to. The comparison is case-sensitive.
- * If not set or empty, it means this rule applies to all actions.
- */
- public $actions;
- /**
- * @var array list of controller IDs that this rule applies to. The comparison is case-sensitive.
- * If not set or empty, it means this rule applies to all controllers.
- */
- public $controllers;
- /**
- * @var array list of roles that this rule applies to. Two special roles are recognized, and
- * they are checked via [[User::isGuest]]:
- *
- * - `?`: matches a guest user (not authenticated yet)
- * - `@`: matches an authenticated user
- *
- * Using additional role names requires RBAC (Role-Based Access Control), and
- * [[User::checkAccess()]] will be called.
- *
- * If this property is not set or empty, it means this rule applies to all roles.
- */
- public $roles;
- /**
- * @var array list of user IP addresses that this rule applies to. An IP address
- * can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
- * For example, '192.168.*' matches all IP addresses in the segment '192.168.'.
- * If not set or empty, it means this rule applies to all IP addresses.
- * @see Request::userIP
- */
- public $ips;
- /**
- * @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to.
- * The request methods must be specified in uppercase.
- * If not set or empty, it means this rule applies to all request methods.
- * @see Request::requestMethod
- */
- public $verbs;
- /**
- * @var callable a callback that will be called to determine if the rule should be applied.
- * The signature of the callback should be as follows:
- *
- * ~~~
- * function ($rule, $action)
- * ~~~
- *
- * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
- * The callback should return a boolean value indicating whether this rule should be applied.
- */
- public $matchCallback;
- /**
- * @var callable a callback that will be called if this rule determines the access to
- * the current action should be denied. If not set, the behavior will be determined by
- * [[AccessControl]].
- *
- * The signature of the callback should be as follows:
- *
- * ~~~
- * function ($rule, $action)
- * ~~~
- *
- * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
- */
- public $denyCallback;
+ /**
+ * @var boolean whether this is an 'allow' rule or 'deny' rule.
+ */
+ public $allow;
+ /**
+ * @var array list of action IDs that this rule applies to. The comparison is case-sensitive.
+ * If not set or empty, it means this rule applies to all actions.
+ */
+ public $actions;
+ /**
+ * @var array list of controller IDs that this rule applies to. The comparison is case-sensitive.
+ * If not set or empty, it means this rule applies to all controllers.
+ */
+ public $controllers;
+ /**
+ * @var array list of roles that this rule applies to. Two special roles are recognized, and
+ * they are checked via [[User::isGuest]]:
+ *
+ * - `?`: matches a guest user (not authenticated yet)
+ * - `@`: matches an authenticated user
+ *
+ * Using additional role names requires RBAC (Role-Based Access Control), and
+ * [[User::checkAccess()]] will be called.
+ *
+ * If this property is not set or empty, it means this rule applies to all roles.
+ */
+ public $roles;
+ /**
+ * @var array list of user IP addresses that this rule applies to. An IP address
+ * can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
+ * For example, '192.168.*' matches all IP addresses in the segment '192.168.'.
+ * If not set or empty, it means this rule applies to all IP addresses.
+ * @see Request::userIP
+ */
+ public $ips;
+ /**
+ * @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to.
+ * The request methods must be specified in uppercase.
+ * If not set or empty, it means this rule applies to all request methods.
+ * @see Request::requestMethod
+ */
+ public $verbs;
+ /**
+ * @var callable a callback that will be called to determine if the rule should be applied.
+ * The signature of the callback should be as follows:
+ *
+ * ~~~
+ * function ($rule, $action)
+ * ~~~
+ *
+ * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
+ * The callback should return a boolean value indicating whether this rule should be applied.
+ */
+ public $matchCallback;
+ /**
+ * @var callable a callback that will be called if this rule determines the access to
+ * the current action should be denied. If not set, the behavior will be determined by
+ * [[AccessControl]].
+ *
+ * The signature of the callback should be as follows:
+ *
+ * ~~~
+ * function ($rule, $action)
+ * ~~~
+ *
+ * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
+ */
+ public $denyCallback;
+ /**
+ * Checks whether the Web user is allowed to perform the specified action.
+ * @param Action $action the action to be performed
+ * @param User $user the user object
+ * @param Request $request
+ * @return boolean|null true if the user is allowed, false if the user is denied, null if the rule does not apply to the user
+ */
+ public function allows($action, $user, $request)
+ {
+ if ($this->matchAction($action)
+ && $this->matchRole($user)
+ && $this->matchIP($request->getUserIP())
+ && $this->matchVerb($request->getMethod())
+ && $this->matchController($action->controller)
+ && $this->matchCustom($action)
+ ) {
+ return $this->allow ? true : false;
+ } else {
+ return null;
+ }
+ }
- /**
- * Checks whether the Web user is allowed to perform the specified action.
- * @param Action $action the action to be performed
- * @param User $user the user object
- * @param Request $request
- * @return boolean|null true if the user is allowed, false if the user is denied, null if the rule does not apply to the user
- */
- public function allows($action, $user, $request)
- {
- if ($this->matchAction($action)
- && $this->matchRole($user)
- && $this->matchIP($request->getUserIP())
- && $this->matchVerb($request->getMethod())
- && $this->matchController($action->controller)
- && $this->matchCustom($action)
- ) {
- return $this->allow ? true : false;
- } else {
- return null;
- }
- }
+ /**
+ * @param Action $action the action
+ * @return boolean whether the rule applies to the action
+ */
+ protected function matchAction($action)
+ {
+ return empty($this->actions) || in_array($action->id, $this->actions, true);
+ }
- /**
- * @param Action $action the action
- * @return boolean whether the rule applies to the action
- */
- protected function matchAction($action)
- {
- return empty($this->actions) || in_array($action->id, $this->actions, true);
- }
+ /**
+ * @param Controller $controller the controller
+ * @return boolean whether the rule applies to the controller
+ */
+ protected function matchController($controller)
+ {
+ return empty($this->controllers) || in_array($controller->uniqueId, $this->controllers, true);
+ }
- /**
- * @param Controller $controller the controller
- * @return boolean whether the rule applies to the controller
- */
- protected function matchController($controller)
- {
- return empty($this->controllers) || in_array($controller->uniqueId, $this->controllers, true);
- }
+ /**
+ * @param User $user the user object
+ * @return boolean whether the rule applies to the role
+ */
+ protected function matchRole($user)
+ {
+ if (empty($this->roles)) {
+ return true;
+ }
+ foreach ($this->roles as $role) {
+ if ($role === '?' && $user->getIsGuest()) {
+ return true;
+ } elseif ($role === '@' && !$user->getIsGuest()) {
+ return true;
+ } elseif ($user->checkAccess($role)) {
+ return true;
+ }
+ }
- /**
- * @param User $user the user object
- * @return boolean whether the rule applies to the role
- */
- protected function matchRole($user)
- {
- if (empty($this->roles)) {
- return true;
- }
- foreach ($this->roles as $role) {
- if ($role === '?' && $user->getIsGuest()) {
- return true;
- } elseif ($role === '@' && !$user->getIsGuest()) {
- return true;
- } elseif ($user->checkAccess($role)) {
- return true;
- }
- }
- return false;
- }
+ return false;
+ }
- /**
- * @param string $ip the IP address
- * @return boolean whether the rule applies to the IP address
- */
- protected function matchIP($ip)
- {
- if (empty($this->ips)) {
- return true;
- }
- foreach ($this->ips as $rule) {
- if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) {
- return true;
- }
- }
- return false;
- }
+ /**
+ * @param string $ip the IP address
+ * @return boolean whether the rule applies to the IP address
+ */
+ protected function matchIP($ip)
+ {
+ if (empty($this->ips)) {
+ return true;
+ }
+ foreach ($this->ips as $rule) {
+ if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) {
+ return true;
+ }
+ }
- /**
- * @param string $verb the request method
- * @return boolean whether the rule applies to the request
- */
- protected function matchVerb($verb)
- {
- return empty($this->verbs) || in_array($verb, $this->verbs, true);
- }
+ return false;
+ }
- /**
- * @param Action $action the action to be performed
- * @return boolean whether the rule should be applied
- */
- protected function matchCustom($action)
- {
- return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action);
- }
+ /**
+ * @param string $verb the request method
+ * @return boolean whether the rule applies to the request
+ */
+ protected function matchVerb($verb)
+ {
+ return empty($this->verbs) || in_array($verb, $this->verbs, true);
+ }
+
+ /**
+ * @param Action $action the action to be performed
+ * @return boolean whether the rule should be applied
+ */
+ protected function matchCustom($action)
+ {
+ return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action);
+ }
}
diff --git a/framework/web/Application.php b/framework/web/Application.php
index f1fcff06f61..f991ec3582e 100644
--- a/framework/web/Application.php
+++ b/framework/web/Application.php
@@ -25,162 +25,162 @@
*/
class Application extends \yii\base\Application
{
- /**
- * @var string the default route of this application. Defaults to 'site'.
- */
- public $defaultRoute = 'site';
- /**
- * @var array the configuration specifying a controller action which should handle
- * all user requests. This is mainly used when the application is in maintenance mode
- * and needs to handle all incoming requests via a single action.
- * The configuration is an array whose first element specifies the route of the action.
- * The rest of the array elements (key-value pairs) specify the parameters to be bound
- * to the action. For example,
- *
- * ~~~
- * [
- * 'offline/notice',
- * 'param1' => 'value1',
- * 'param2' => 'value2',
- * ]
- * ~~~
- *
- * Defaults to null, meaning catch-all is not used.
- */
- public $catchAll;
- /**
- * @var Controller the currently active controller instance
- */
- public $controller;
+ /**
+ * @var string the default route of this application. Defaults to 'site'.
+ */
+ public $defaultRoute = 'site';
+ /**
+ * @var array the configuration specifying a controller action which should handle
+ * all user requests. This is mainly used when the application is in maintenance mode
+ * and needs to handle all incoming requests via a single action.
+ * The configuration is an array whose first element specifies the route of the action.
+ * The rest of the array elements (key-value pairs) specify the parameters to be bound
+ * to the action. For example,
+ *
+ * ~~~
+ * [
+ * 'offline/notice',
+ * 'param1' => 'value1',
+ * 'param2' => 'value2',
+ * ]
+ * ~~~
+ *
+ * Defaults to null, meaning catch-all is not used.
+ */
+ public $catchAll;
+ /**
+ * @var Controller the currently active controller instance
+ */
+ public $controller;
+ /**
+ * @inheritdoc
+ */
+ public function preloadComponents()
+ {
+ parent::preloadComponents();
+ $request = $this->getRequest();
+ Yii::setAlias('@webroot', dirname($request->getScriptFile()));
+ Yii::setAlias('@web', $request->getBaseUrl());
+ }
- /**
- * @inheritdoc
- */
- public function preloadComponents()
- {
- parent::preloadComponents();
- $request = $this->getRequest();
- Yii::setAlias('@webroot', dirname($request->getScriptFile()));
- Yii::setAlias('@web', $request->getBaseUrl());
- }
+ /**
+ * Handles the specified request.
+ * @param Request $request the request to be handled
+ * @return Response the resulting response
+ * @throws NotFoundHttpException if the requested route is invalid
+ */
+ public function handleRequest($request)
+ {
+ if (empty($this->catchAll)) {
+ list ($route, $params) = $request->resolve();
+ } else {
+ $route = $this->catchAll[0];
+ $params = array_splice($this->catchAll, 1);
+ }
+ try {
+ Yii::trace("Route requested: '$route'", __METHOD__);
+ $this->requestedRoute = $route;
+ $result = $this->runAction($route, $params);
+ if ($result instanceof Response) {
+ return $result;
+ } else {
+ $response = $this->getResponse();
+ if ($result !== null) {
+ $response->data = $result;
+ }
- /**
- * Handles the specified request.
- * @param Request $request the request to be handled
- * @return Response the resulting response
- * @throws NotFoundHttpException if the requested route is invalid
- */
- public function handleRequest($request)
- {
- if (empty($this->catchAll)) {
- list ($route, $params) = $request->resolve();
- } else {
- $route = $this->catchAll[0];
- $params = array_splice($this->catchAll, 1);
- }
- try {
- Yii::trace("Route requested: '$route'", __METHOD__);
- $this->requestedRoute = $route;
- $result = $this->runAction($route, $params);
- if ($result instanceof Response) {
- return $result;
- } else {
- $response = $this->getResponse();
- if ($result !== null) {
- $response->data = $result;
- }
- return $response;
- }
- } catch (InvalidRouteException $e) {
- throw new NotFoundHttpException($e->getMessage(), $e->getCode(), $e);
- }
- }
+ return $response;
+ }
+ } catch (InvalidRouteException $e) {
+ throw new NotFoundHttpException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
- private $_homeUrl;
+ private $_homeUrl;
- /**
- * @return string the homepage URL
- */
- public function getHomeUrl()
- {
- if ($this->_homeUrl === null) {
- if ($this->getUrlManager()->showScriptName) {
- return $this->getRequest()->getScriptUrl();
- } else {
- return $this->getRequest()->getBaseUrl() . '/';
- }
- } else {
- return $this->_homeUrl;
- }
- }
+ /**
+ * @return string the homepage URL
+ */
+ public function getHomeUrl()
+ {
+ if ($this->_homeUrl === null) {
+ if ($this->getUrlManager()->showScriptName) {
+ return $this->getRequest()->getScriptUrl();
+ } else {
+ return $this->getRequest()->getBaseUrl() . '/';
+ }
+ } else {
+ return $this->_homeUrl;
+ }
+ }
- /**
- * @param string $value the homepage URL
- */
- public function setHomeUrl($value)
- {
- $this->_homeUrl = $value;
- }
+ /**
+ * @param string $value the homepage URL
+ */
+ public function setHomeUrl($value)
+ {
+ $this->_homeUrl = $value;
+ }
- /**
- * Returns the request component.
- * @return Request the request component
- */
- public function getRequest()
- {
- return $this->getComponent('request');
- }
+ /**
+ * Returns the request component.
+ * @return Request the request component
+ */
+ public function getRequest()
+ {
+ return $this->getComponent('request');
+ }
- /**
- * Returns the response component.
- * @return Response the response component
- */
- public function getResponse()
- {
- return $this->getComponent('response');
- }
+ /**
+ * Returns the response component.
+ * @return Response the response component
+ */
+ public function getResponse()
+ {
+ return $this->getComponent('response');
+ }
- /**
- * Returns the session component.
- * @return Session the session component
- */
- public function getSession()
- {
- return $this->getComponent('session');
- }
+ /**
+ * Returns the session component.
+ * @return Session the session component
+ */
+ public function getSession()
+ {
+ return $this->getComponent('session');
+ }
- /**
- * Returns the user component.
- * @return User the user component
- */
- public function getUser()
- {
- return $this->getComponent('user');
- }
+ /**
+ * Returns the user component.
+ * @return User the user component
+ */
+ public function getUser()
+ {
+ return $this->getComponent('user');
+ }
- /**
- * Returns the asset manager.
- * @return AssetManager the asset manager component
- */
- public function getAssetManager()
- {
- return $this->getComponent('assetManager');
- }
+ /**
+ * Returns the asset manager.
+ * @return AssetManager the asset manager component
+ */
+ public function getAssetManager()
+ {
+ return $this->getComponent('assetManager');
+ }
- /**
- * Registers the core application components.
- * @see setComponents
- */
- public function registerCoreComponents()
- {
- parent::registerCoreComponents();
- $this->setComponents([
- 'request' => ['class' => 'yii\web\Request'],
- 'response' => ['class' => 'yii\web\Response'],
- 'session' => ['class' => 'yii\web\Session'],
- 'user' => ['class' => 'yii\web\User'],
- 'assetManager' => ['class' => 'yii\web\AssetManager'],
- ]);
- }
+ /**
+ * Registers the core application components.
+ * @see setComponents
+ */
+ public function registerCoreComponents()
+ {
+ parent::registerCoreComponents();
+ $this->setComponents([
+ 'request' => ['class' => 'yii\web\Request'],
+ 'response' => ['class' => 'yii\web\Response'],
+ 'session' => ['class' => 'yii\web\Session'],
+ 'user' => ['class' => 'yii\web\User'],
+ 'assetManager' => ['class' => 'yii\web\AssetManager'],
+ ]);
+ }
}
diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php
index 8fcdf2cfcca..3321bf511e2 100644
--- a/framework/web/AssetBundle.php
+++ b/framework/web/AssetBundle.php
@@ -25,168 +25,168 @@
*/
class AssetBundle extends Object
{
- /**
- * @var string the root directory of the source asset files. A source asset file
- * is a file that is part of your source code repository of your Web application.
- *
- * You must set this property if the directory containing the source asset files
- * is not Web accessible (this is usually the case for extensions).
- *
- * By setting this property, the asset manager will publish the source asset files
- * to a Web-accessible directory [[basePath]].
- *
- * You can use either a directory or an alias of the directory.
- */
- public $sourcePath;
- /**
- * @var string the Web-accessible directory that contains the asset files in this bundle.
- *
- * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
- * when it publishes the asset files from [[sourcePath]].
- *
- * If the bundle contains any assets that are specified in terms of relative file path,
- * then this property must be set either manually or automatically (by [[AssetManager]] via
- * asset publishing).
- *
- * You can use either a directory or an alias of the directory.
- */
- public $basePath;
- /**
- * @var string the base URL that will be prefixed to the asset files for them to
- * be accessed via Web server.
- *
- * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
- * when it publishes the asset files from [[sourcePath]].
- *
- * If the bundle contains any assets that are specified in terms of relative file path,
- * then this property must be set either manually or automatically (by asset manager via
- * asset publishing).
- *
- * You can use either a URL or an alias of the URL.
- */
- public $baseUrl;
- /**
- * @var array list of bundle class names that this bundle depends on.
- *
- * For example:
- *
- * ```php
- * public $depends = [
- * 'yii\web\YiiAsset',
- * 'yii\bootstrap\BootstrapAsset',
- * ];
- * ```
- */
- public $depends = [];
- /**
- * @var array list of JavaScript files that this bundle contains. Each JavaScript file can
- * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
- * an external JavaScript file.
- *
- * Note that only forward slash "/" can be used as directory separator.
- */
- public $js = [];
- /**
- * @var array list of CSS files that this bundle contains. Each CSS file can
- * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
- * an external CSS file.
- *
- * Note that only forward slash "/" can be used as directory separator.
- */
- public $css = [];
- /**
- * @var array the options that will be passed to [[\yii\web\View::registerJsFile()]]
- * when registering the JS files in this bundle.
- */
- public $jsOptions = [];
- /**
- * @var array the options that will be passed to [[\yii\web\View::registerCssFile()]]
- * when registering the CSS files in this bundle.
- */
- public $cssOptions = [];
- /**
- * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle
- * is being published.
- */
- public $publishOptions = [];
+ /**
+ * @var string the root directory of the source asset files. A source asset file
+ * is a file that is part of your source code repository of your Web application.
+ *
+ * You must set this property if the directory containing the source asset files
+ * is not Web accessible (this is usually the case for extensions).
+ *
+ * By setting this property, the asset manager will publish the source asset files
+ * to a Web-accessible directory [[basePath]].
+ *
+ * You can use either a directory or an alias of the directory.
+ */
+ public $sourcePath;
+ /**
+ * @var string the Web-accessible directory that contains the asset files in this bundle.
+ *
+ * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
+ * when it publishes the asset files from [[sourcePath]].
+ *
+ * If the bundle contains any assets that are specified in terms of relative file path,
+ * then this property must be set either manually or automatically (by [[AssetManager]] via
+ * asset publishing).
+ *
+ * You can use either a directory or an alias of the directory.
+ */
+ public $basePath;
+ /**
+ * @var string the base URL that will be prefixed to the asset files for them to
+ * be accessed via Web server.
+ *
+ * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
+ * when it publishes the asset files from [[sourcePath]].
+ *
+ * If the bundle contains any assets that are specified in terms of relative file path,
+ * then this property must be set either manually or automatically (by asset manager via
+ * asset publishing).
+ *
+ * You can use either a URL or an alias of the URL.
+ */
+ public $baseUrl;
+ /**
+ * @var array list of bundle class names that this bundle depends on.
+ *
+ * For example:
+ *
+ * ```php
+ * public $depends = [
+ * 'yii\web\YiiAsset',
+ * 'yii\bootstrap\BootstrapAsset',
+ * ];
+ * ```
+ */
+ public $depends = [];
+ /**
+ * @var array list of JavaScript files that this bundle contains. Each JavaScript file can
+ * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
+ * an external JavaScript file.
+ *
+ * Note that only forward slash "/" can be used as directory separator.
+ */
+ public $js = [];
+ /**
+ * @var array list of CSS files that this bundle contains. Each CSS file can
+ * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
+ * an external CSS file.
+ *
+ * Note that only forward slash "/" can be used as directory separator.
+ */
+ public $css = [];
+ /**
+ * @var array the options that will be passed to [[\yii\web\View::registerJsFile()]]
+ * when registering the JS files in this bundle.
+ */
+ public $jsOptions = [];
+ /**
+ * @var array the options that will be passed to [[\yii\web\View::registerCssFile()]]
+ * when registering the CSS files in this bundle.
+ */
+ public $cssOptions = [];
+ /**
+ * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle
+ * is being published.
+ */
+ public $publishOptions = [];
- /**
- * @param View $view
- * @return static the registered asset bundle instance
- */
- public static function register($view)
- {
- return $view->registerAssetBundle(get_called_class());
- }
+ /**
+ * @param View $view
+ * @return static the registered asset bundle instance
+ */
+ public static function register($view)
+ {
+ return $view->registerAssetBundle(get_called_class());
+ }
- /**
- * Initializes the bundle.
- * If you override this method, make sure you call the parent implementation in the last.
- */
- public function init()
- {
- if ($this->sourcePath !== null) {
- $this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\');
- }
- if ($this->basePath !== null) {
- $this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\');
- }
- if ($this->baseUrl !== null) {
- $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
- }
- }
+ /**
+ * Initializes the bundle.
+ * If you override this method, make sure you call the parent implementation in the last.
+ */
+ public function init()
+ {
+ if ($this->sourcePath !== null) {
+ $this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\');
+ }
+ if ($this->basePath !== null) {
+ $this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\');
+ }
+ if ($this->baseUrl !== null) {
+ $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
+ }
+ }
- /**
- * Registers the CSS and JS files with the given view.
- * @param \yii\web\View $view the view that the asset files are to be registered with.
- */
- public function registerAssetFiles($view)
- {
- foreach ($this->js as $js) {
- if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
- $view->registerJsFile($this->baseUrl . '/' . $js, [], $this->jsOptions);
- } else {
- $view->registerJsFile($js, [], $this->jsOptions);
- }
- }
- foreach ($this->css as $css) {
- if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
- $view->registerCssFile($this->baseUrl . '/' . $css, [], $this->cssOptions);
- } else {
- $view->registerCssFile($css, [], $this->cssOptions);
- }
- }
- }
+ /**
+ * Registers the CSS and JS files with the given view.
+ * @param \yii\web\View $view the view that the asset files are to be registered with.
+ */
+ public function registerAssetFiles($view)
+ {
+ foreach ($this->js as $js) {
+ if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
+ $view->registerJsFile($this->baseUrl . '/' . $js, [], $this->jsOptions);
+ } else {
+ $view->registerJsFile($js, [], $this->jsOptions);
+ }
+ }
+ foreach ($this->css as $css) {
+ if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
+ $view->registerCssFile($this->baseUrl . '/' . $css, [], $this->cssOptions);
+ } else {
+ $view->registerCssFile($css, [], $this->cssOptions);
+ }
+ }
+ }
- /**
- * Publishes the asset bundle if its source code is not under Web-accessible directory.
- * It will also try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding
- * CSS or JS files using [[AssetManager::converter|asset converter]].
- * @param AssetManager $am the asset manager to perform the asset publishing
- */
- public function publish($am)
- {
- if ($this->sourcePath !== null && !isset($this->basePath, $this->baseUrl)) {
- list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions);
- }
- $converter = $am->getConverter();
- foreach ($this->js as $i => $js) {
- if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
- if (isset($this->basePath, $this->baseUrl)) {
- $this->js[$i] = $converter->convert($js, $this->basePath, $this->baseUrl);
- } else {
- $this->js[$i] = '/' . $js;
- }
- }
- }
- foreach ($this->css as $i => $css) {
- if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
- if (isset($this->basePath, $this->baseUrl)) {
- $this->css[$i] = $converter->convert($css, $this->basePath, $this->baseUrl);
- } else {
- $this->css[$i] = '/' . $css;
- }
- }
- }
- }
+ /**
+ * Publishes the asset bundle if its source code is not under Web-accessible directory.
+ * It will also try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding
+ * CSS or JS files using [[AssetManager::converter|asset converter]].
+ * @param AssetManager $am the asset manager to perform the asset publishing
+ */
+ public function publish($am)
+ {
+ if ($this->sourcePath !== null && !isset($this->basePath, $this->baseUrl)) {
+ list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions);
+ }
+ $converter = $am->getConverter();
+ foreach ($this->js as $i => $js) {
+ if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
+ if (isset($this->basePath, $this->baseUrl)) {
+ $this->js[$i] = $converter->convert($js, $this->basePath, $this->baseUrl);
+ } else {
+ $this->js[$i] = '/' . $js;
+ }
+ }
+ }
+ foreach ($this->css as $i => $css) {
+ if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
+ if (isset($this->basePath, $this->baseUrl)) {
+ $this->css[$i] = $converter->convert($css, $this->basePath, $this->baseUrl);
+ } else {
+ $this->css[$i] = '/' . $css;
+ }
+ }
+ }
+ }
}
diff --git a/framework/web/AssetConverter.php b/framework/web/AssetConverter.php
index 79e5e4d3a0c..d679e042dea 100644
--- a/framework/web/AssetConverter.php
+++ b/framework/web/AssetConverter.php
@@ -21,79 +21,82 @@
*/
class AssetConverter extends Component implements AssetConverterInterface
{
- /**
- * @var array the commands that are used to perform the asset conversion.
- * The keys are the asset file extension names, and the values are the corresponding
- * target script types (either "css" or "js") and the commands used for the conversion.
- */
- public $commands = [
- 'less' => ['css', 'lessc {from} {to} --no-color'],
- 'scss' => ['css', 'sass {from} {to}'],
- 'sass' => ['css', 'sass {from} {to}'],
- 'styl' => ['js', 'stylus < {from} > {to}'],
- 'coffee' => ['js', 'coffee -p {from} > {to}'],
- 'ts' => ['js', 'tsc --out {to} {from}'],
- ];
+ /**
+ * @var array the commands that are used to perform the asset conversion.
+ * The keys are the asset file extension names, and the values are the corresponding
+ * target script types (either "css" or "js") and the commands used for the conversion.
+ */
+ public $commands = [
+ 'less' => ['css', 'lessc {from} {to} --no-color'],
+ 'scss' => ['css', 'sass {from} {to}'],
+ 'sass' => ['css', 'sass {from} {to}'],
+ 'styl' => ['js', 'stylus < {from} > {to}'],
+ 'coffee' => ['js', 'coffee -p {from} > {to}'],
+ 'ts' => ['js', 'tsc --out {to} {from}'],
+ ];
- /**
- * Converts a given asset file into a CSS or JS file.
- * @param string $asset the asset file path, relative to $basePath
- * @param string $basePath the directory the $asset is relative to.
- * @return string the converted asset file path, relative to $basePath.
- */
- public function convert($asset, $basePath)
- {
- $pos = strrpos($asset, '.');
- if ($pos !== false) {
- $ext = substr($asset, $pos + 1);
- if (isset($this->commands[$ext])) {
- list ($ext, $command) = $this->commands[$ext];
- $result = substr($asset, 0, $pos + 1) . $ext;
- if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) {
- $this->runCommand($command, $basePath, $asset, $result);
- }
- return $result;
- }
- }
- return $asset;
- }
+ /**
+ * Converts a given asset file into a CSS or JS file.
+ * @param string $asset the asset file path, relative to $basePath
+ * @param string $basePath the directory the $asset is relative to.
+ * @return string the converted asset file path, relative to $basePath.
+ */
+ public function convert($asset, $basePath)
+ {
+ $pos = strrpos($asset, '.');
+ if ($pos !== false) {
+ $ext = substr($asset, $pos + 1);
+ if (isset($this->commands[$ext])) {
+ list ($ext, $command) = $this->commands[$ext];
+ $result = substr($asset, 0, $pos + 1) . $ext;
+ if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) {
+ $this->runCommand($command, $basePath, $asset, $result);
+ }
- /**
- * Runs a command to convert asset files.
- * @param string $command the command to run
- * @param string $basePath asset base path and command working directory
- * @param string $asset the name of the asset file
- * @param string $result the name of the file to be generated by the converter command
- * @return boolean true on success, false on failure. Failures will be logged.
- * @throws \yii\base\Exception when the command fails and YII_DEBUG is true.
- * In production mode the error will be logged.
- */
- protected function runCommand($command, $basePath, $asset, $result)
- {
- $command = strtr($command, [
- '{from}' => escapeshellarg("$basePath/$asset"),
- '{to}' => escapeshellarg("$basePath/$result"),
- ]);
- $descriptor = [
- 1 => ['pipe', 'w'],
- 2 => ['pipe', 'w'],
- ];
- $pipes = [];
- $proc = proc_open($command, $descriptor, $pipes, $basePath);
- $stdout = stream_get_contents($pipes[1]);
- $stderr = stream_get_contents($pipes[2]);
- foreach ($pipes as $pipe) {
- fclose($pipe);
- }
- $status = proc_close($proc);
+ return $result;
+ }
+ }
- if ($status === 0) {
- Yii::trace("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
- } elseif (YII_DEBUG) {
- throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
- } else {
- Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
- }
- return $status === 0;
- }
+ return $asset;
+ }
+
+ /**
+ * Runs a command to convert asset files.
+ * @param string $command the command to run
+ * @param string $basePath asset base path and command working directory
+ * @param string $asset the name of the asset file
+ * @param string $result the name of the file to be generated by the converter command
+ * @return boolean true on success, false on failure. Failures will be logged.
+ * @throws \yii\base\Exception when the command fails and YII_DEBUG is true.
+ * In production mode the error will be logged.
+ */
+ protected function runCommand($command, $basePath, $asset, $result)
+ {
+ $command = strtr($command, [
+ '{from}' => escapeshellarg("$basePath/$asset"),
+ '{to}' => escapeshellarg("$basePath/$result"),
+ ]);
+ $descriptor = [
+ 1 => ['pipe', 'w'],
+ 2 => ['pipe', 'w'],
+ ];
+ $pipes = [];
+ $proc = proc_open($command, $descriptor, $pipes, $basePath);
+ $stdout = stream_get_contents($pipes[1]);
+ $stderr = stream_get_contents($pipes[2]);
+ foreach ($pipes as $pipe) {
+ fclose($pipe);
+ }
+ $status = proc_close($proc);
+
+ if ($status === 0) {
+ Yii::trace("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
+ } elseif (YII_DEBUG) {
+ throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
+ } else {
+ Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
+ }
+
+ return $status === 0;
+ }
}
diff --git a/framework/web/AssetConverterInterface.php b/framework/web/AssetConverterInterface.php
index 51309c61f88..8a0e6797cd5 100644
--- a/framework/web/AssetConverterInterface.php
+++ b/framework/web/AssetConverterInterface.php
@@ -15,11 +15,11 @@
*/
interface AssetConverterInterface
{
- /**
- * Converts a given asset file into a CSS or JS file.
- * @param string $asset the asset file path, relative to $basePath
- * @param string $basePath the directory the $asset is relative to.
- * @return string the converted asset file path, relative to $basePath.
- */
- public function convert($asset, $basePath);
+ /**
+ * Converts a given asset file into a CSS or JS file.
+ * @param string $asset the asset file path, relative to $basePath
+ * @param string $basePath the directory the $asset is relative to.
+ * @return string the converted asset file path, relative to $basePath.
+ */
+ public function convert($asset, $basePath);
}
diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php
index c600223ddfa..745fdf185dc 100644
--- a/framework/web/AssetManager.php
+++ b/framework/web/AssetManager.php
@@ -40,312 +40,314 @@
*/
class AssetManager extends Component
{
- /**
- * @var array list of available asset bundles. The keys are the class names (without leading backslash)
- * of the asset bundles, and the values are either the configuration arrays for creating the [[AssetBundle]]
- * objects or the corresponding asset bundle instances. For example, the following code disables
- * the bootstrap css file used by Bootstrap widgets (because you want to use your own styles):
- *
- * ~~~
- * [
- * 'yii\bootstrap\BootstrapAsset' => [
- * 'css' => [],
- * ],
- * ]
- * ~~~
- */
- public $bundles = [];
- /**
- * @return string the root directory storing the published asset files.
- */
- public $basePath = '@webroot/assets';
- /**
- * @return string the base URL through which the published asset files can be accessed.
- */
- public $baseUrl = '@web/assets';
- /**
- * @var boolean whether to use symbolic link to publish asset files. Defaults to false, meaning
- * asset files are copied to [[basePath]]. Using symbolic links has the benefit that the published
- * assets will always be consistent with the source assets and there is no copy operation required.
- * This is especially useful during development.
- *
- * However, there are special requirements for hosting environments in order to use symbolic links.
- * In particular, symbolic links are supported only on Linux/Unix, and Windows Vista/2008 or greater.
- *
- * Moreover, some Web servers need to be properly configured so that the linked assets are accessible
- * to Web users. For example, for Apache Web server, the following configuration directive should be added
- * for the Web folder:
- *
- * ~~~
- * Options FollowSymLinks
- * ~~~
- */
- public $linkAssets = false;
- /**
- * @var integer the permission to be set for newly published asset files.
- * This value will be used by PHP chmod() function. No umask will be applied.
- * If not set, the permission will be determined by the current environment.
- */
- public $fileMode;
- /**
- * @var integer the permission to be set for newly generated asset directories.
- * This value will be used by PHP chmod() function. No umask will be applied.
- * Defaults to 0775, meaning the directory is read-writable by owner and group,
- * but read-only for other users.
- */
- public $dirMode = 0775;
+ /**
+ * @var array list of available asset bundles. The keys are the class names (without leading backslash)
+ * of the asset bundles, and the values are either the configuration arrays for creating the [[AssetBundle]]
+ * objects or the corresponding asset bundle instances. For example, the following code disables
+ * the bootstrap css file used by Bootstrap widgets (because you want to use your own styles):
+ *
+ * ~~~
+ * [
+ * 'yii\bootstrap\BootstrapAsset' => [
+ * 'css' => [],
+ * ],
+ * ]
+ * ~~~
+ */
+ public $bundles = [];
+ /**
+ * @return string the root directory storing the published asset files.
+ */
+ public $basePath = '@webroot/assets';
+ /**
+ * @return string the base URL through which the published asset files can be accessed.
+ */
+ public $baseUrl = '@web/assets';
+ /**
+ * @var boolean whether to use symbolic link to publish asset files. Defaults to false, meaning
+ * asset files are copied to [[basePath]]. Using symbolic links has the benefit that the published
+ * assets will always be consistent with the source assets and there is no copy operation required.
+ * This is especially useful during development.
+ *
+ * However, there are special requirements for hosting environments in order to use symbolic links.
+ * In particular, symbolic links are supported only on Linux/Unix, and Windows Vista/2008 or greater.
+ *
+ * Moreover, some Web servers need to be properly configured so that the linked assets are accessible
+ * to Web users. For example, for Apache Web server, the following configuration directive should be added
+ * for the Web folder:
+ *
+ * ~~~
+ * Options FollowSymLinks
+ * ~~~
+ */
+ public $linkAssets = false;
+ /**
+ * @var integer the permission to be set for newly published asset files.
+ * This value will be used by PHP chmod() function. No umask will be applied.
+ * If not set, the permission will be determined by the current environment.
+ */
+ public $fileMode;
+ /**
+ * @var integer the permission to be set for newly generated asset directories.
+ * This value will be used by PHP chmod() function. No umask will be applied.
+ * Defaults to 0775, meaning the directory is read-writable by owner and group,
+ * but read-only for other users.
+ */
+ public $dirMode = 0775;
- /**
- * Initializes the component.
- * @throws InvalidConfigException if [[basePath]] is invalid
- */
- public function init()
- {
- parent::init();
- $this->basePath = Yii::getAlias($this->basePath);
- if (!is_dir($this->basePath)) {
- throw new InvalidConfigException("The directory does not exist: {$this->basePath}");
- } elseif (!is_writable($this->basePath)) {
- throw new InvalidConfigException("The directory is not writable by the Web process: {$this->basePath}");
- } else {
- $this->basePath = realpath($this->basePath);
- }
- $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
- }
+ /**
+ * Initializes the component.
+ * @throws InvalidConfigException if [[basePath]] is invalid
+ */
+ public function init()
+ {
+ parent::init();
+ $this->basePath = Yii::getAlias($this->basePath);
+ if (!is_dir($this->basePath)) {
+ throw new InvalidConfigException("The directory does not exist: {$this->basePath}");
+ } elseif (!is_writable($this->basePath)) {
+ throw new InvalidConfigException("The directory is not writable by the Web process: {$this->basePath}");
+ } else {
+ $this->basePath = realpath($this->basePath);
+ }
+ $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
+ }
- /**
- * Returns the named asset bundle.
- *
- * This method will first look for the bundle in [[bundles]]. If not found,
- * it will treat `$name` as the class of the asset bundle and create a new instance of it.
- *
- * @param string $name the class name of the asset bundle
- * @param boolean $publish whether to publish the asset files in the asset bundle before it is returned.
- * If you set this false, you must manually call `AssetBundle::publish()` to publish the asset files.
- * @return AssetBundle the asset bundle instance
- * @throws InvalidConfigException if $name does not refer to a valid asset bundle
- */
- public function getBundle($name, $publish = true)
- {
- if (isset($this->bundles[$name])) {
- if ($this->bundles[$name] instanceof AssetBundle) {
- return $this->bundles[$name];
- } elseif (is_array($this->bundles[$name])) {
- $bundle = Yii::createObject(array_merge(['class' => $name], $this->bundles[$name]));
- } else {
- throw new InvalidConfigException("Invalid asset bundle: $name");
- }
- } else {
- $bundle = Yii::createObject($name);
- }
- if ($publish) {
- /** @var AssetBundle $bundle */
- $bundle->publish($this);
- }
- return $this->bundles[$name] = $bundle;
- }
+ /**
+ * Returns the named asset bundle.
+ *
+ * This method will first look for the bundle in [[bundles]]. If not found,
+ * it will treat `$name` as the class of the asset bundle and create a new instance of it.
+ *
+ * @param string $name the class name of the asset bundle
+ * @param boolean $publish whether to publish the asset files in the asset bundle before it is returned.
+ * If you set this false, you must manually call `AssetBundle::publish()` to publish the asset files.
+ * @return AssetBundle the asset bundle instance
+ * @throws InvalidConfigException if $name does not refer to a valid asset bundle
+ */
+ public function getBundle($name, $publish = true)
+ {
+ if (isset($this->bundles[$name])) {
+ if ($this->bundles[$name] instanceof AssetBundle) {
+ return $this->bundles[$name];
+ } elseif (is_array($this->bundles[$name])) {
+ $bundle = Yii::createObject(array_merge(['class' => $name], $this->bundles[$name]));
+ } else {
+ throw new InvalidConfigException("Invalid asset bundle: $name");
+ }
+ } else {
+ $bundle = Yii::createObject($name);
+ }
+ if ($publish) {
+ /** @var AssetBundle $bundle */
+ $bundle->publish($this);
+ }
- private $_converter;
+ return $this->bundles[$name] = $bundle;
+ }
- /**
- * Returns the asset converter.
- * @return AssetConverterInterface the asset converter.
- */
- public function getConverter()
- {
- if ($this->_converter === null) {
- $this->_converter = Yii::createObject(AssetConverter::className());
- } elseif (is_array($this->_converter) || is_string($this->_converter)) {
- if (is_array($this->_converter) && !isset($this->_converter['class'])) {
- $this->_converter['class'] = AssetConverter::className();
- }
- $this->_converter = Yii::createObject($this->_converter);
- }
- return $this->_converter;
- }
+ private $_converter;
- /**
- * Sets the asset converter.
- * @param array|AssetConverterInterface $value the asset converter. This can be either
- * an object implementing the [[AssetConverterInterface]], or a configuration
- * array that can be used to create the asset converter object.
- */
- public function setConverter($value)
- {
- $this->_converter = $value;
- }
+ /**
+ * Returns the asset converter.
+ * @return AssetConverterInterface the asset converter.
+ */
+ public function getConverter()
+ {
+ if ($this->_converter === null) {
+ $this->_converter = Yii::createObject(AssetConverter::className());
+ } elseif (is_array($this->_converter) || is_string($this->_converter)) {
+ if (is_array($this->_converter) && !isset($this->_converter['class'])) {
+ $this->_converter['class'] = AssetConverter::className();
+ }
+ $this->_converter = Yii::createObject($this->_converter);
+ }
- /**
- * @var array published assets
- */
- private $_published = [];
+ return $this->_converter;
+ }
- /**
- * Publishes a file or a directory.
- *
- * This method will copy the specified file or directory to [[basePath]] so that
- * it can be accessed via the Web server.
- *
- * If the asset is a file, its file modification time will be checked to avoid
- * unnecessary file copying.
- *
- * If the asset is a directory, all files and subdirectories under it will be published recursively.
- * Note, in case $forceCopy is false the method only checks the existence of the target
- * directory to avoid repetitive copying (which is very expensive).
- *
- * By default, when publishing a directory, subdirectories and files whose name starts with a dot "."
- * will NOT be published. If you want to change this behavior, you may specify the "beforeCopy" option
- * as explained in the `$options` parameter.
- *
- * Note: On rare scenario, a race condition can develop that will lead to a
- * one-time-manifestation of a non-critical problem in the creation of the directory
- * that holds the published assets. This problem can be avoided altogether by 'requesting'
- * in advance all the resources that are supposed to trigger a 'publish()' call, and doing
- * that in the application deployment phase, before system goes live. See more in the following
- * discussion: http://code.google.com/p/yii/issues/detail?id=2579
- *
- * @param string $path the asset (file or directory) to be published
- * @param array $options the options to be applied when publishing a directory.
- * The following options are supported:
- *
- * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
- * This option is used only when publishing a directory. If the callback returns false, the copy
- * operation for the sub-directory or file will be cancelled.
- * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
- * file to be copied from, while `$to` is the copy target.
- * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied.
- * This option is used only when publishing a directory. The signature of the callback is similar to that
- * of `beforeCopy`.
- * - forceCopy: boolean, whether the directory being published should be copied even if
- * it is found in the target directory. This option is used only when publishing a directory.
- * You may want to set this to be true during the development stage to make sure the published
- * directory is always up-to-date. Do not set this to true on production servers as it will
- * significantly degrade the performance.
- * @return array the path (directory or file path) and the URL that the asset is published as.
- * @throws InvalidParamException if the asset to be published does not exist.
- */
- public function publish($path, $options = [])
- {
- $path = Yii::getAlias($path);
+ /**
+ * Sets the asset converter.
+ * @param array|AssetConverterInterface $value the asset converter. This can be either
+ * an object implementing the [[AssetConverterInterface]], or a configuration
+ * array that can be used to create the asset converter object.
+ */
+ public function setConverter($value)
+ {
+ $this->_converter = $value;
+ }
- if (isset($this->_published[$path])) {
- return $this->_published[$path];
- }
+ /**
+ * @var array published assets
+ */
+ private $_published = [];
- if (!is_string($path) || ($src = realpath($path)) === false) {
- throw new InvalidParamException("The file or directory to be published does not exist: $path");
- }
+ /**
+ * Publishes a file or a directory.
+ *
+ * This method will copy the specified file or directory to [[basePath]] so that
+ * it can be accessed via the Web server.
+ *
+ * If the asset is a file, its file modification time will be checked to avoid
+ * unnecessary file copying.
+ *
+ * If the asset is a directory, all files and subdirectories under it will be published recursively.
+ * Note, in case $forceCopy is false the method only checks the existence of the target
+ * directory to avoid repetitive copying (which is very expensive).
+ *
+ * By default, when publishing a directory, subdirectories and files whose name starts with a dot "."
+ * will NOT be published. If you want to change this behavior, you may specify the "beforeCopy" option
+ * as explained in the `$options` parameter.
+ *
+ * Note: On rare scenario, a race condition can develop that will lead to a
+ * one-time-manifestation of a non-critical problem in the creation of the directory
+ * that holds the published assets. This problem can be avoided altogether by 'requesting'
+ * in advance all the resources that are supposed to trigger a 'publish()' call, and doing
+ * that in the application deployment phase, before system goes live. See more in the following
+ * discussion: http://code.google.com/p/yii/issues/detail?id=2579
+ *
+ * @param string $path the asset (file or directory) to be published
+ * @param array $options the options to be applied when publishing a directory.
+ * The following options are supported:
+ *
+ * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
+ * This option is used only when publishing a directory. If the callback returns false, the copy
+ * operation for the sub-directory or file will be cancelled.
+ * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
+ * file to be copied from, while `$to` is the copy target.
+ * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied.
+ * This option is used only when publishing a directory. The signature of the callback is similar to that
+ * of `beforeCopy`.
+ * - forceCopy: boolean, whether the directory being published should be copied even if
+ * it is found in the target directory. This option is used only when publishing a directory.
+ * You may want to set this to be true during the development stage to make sure the published
+ * directory is always up-to-date. Do not set this to true on production servers as it will
+ * significantly degrade the performance.
+ * @return array the path (directory or file path) and the URL that the asset is published as.
+ * @throws InvalidParamException if the asset to be published does not exist.
+ */
+ public function publish($path, $options = [])
+ {
+ $path = Yii::getAlias($path);
- if (is_file($src)) {
- $dir = $this->hash(dirname($src) . filemtime($src));
- $fileName = basename($src);
- $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir;
- $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName;
+ if (isset($this->_published[$path])) {
+ return $this->_published[$path];
+ }
- if (!is_dir($dstDir)) {
- FileHelper::createDirectory($dstDir, $this->dirMode, true);
- }
+ if (!is_string($path) || ($src = realpath($path)) === false) {
+ throw new InvalidParamException("The file or directory to be published does not exist: $path");
+ }
- if ($this->linkAssets) {
- if (!is_file($dstFile)) {
- symlink($src, $dstFile);
- }
- } elseif (@filemtime($dstFile) < @filemtime($src)) {
- copy($src, $dstFile);
- if ($this->fileMode !== null) {
- @chmod($dstFile, $this->fileMode);
- }
- }
+ if (is_file($src)) {
+ $dir = $this->hash(dirname($src) . filemtime($src));
+ $fileName = basename($src);
+ $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir;
+ $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName;
- return $this->_published[$path] = [$dstFile, $this->baseUrl . "/$dir/$fileName"];
- } else {
- $dir = $this->hash($src . filemtime($src));
- $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir;
- if ($this->linkAssets) {
- if (!is_dir($dstDir)) {
- symlink($src, $dstDir);
- }
- } elseif (!is_dir($dstDir) || !empty($options['forceCopy'])) {
- $opts = [
- 'dirMode' => $this->dirMode,
- 'fileMode' => $this->fileMode,
- ];
- if (isset($options['beforeCopy'])) {
- $opts['beforeCopy'] = $options['beforeCopy'];
- } else {
- $opts['beforeCopy'] = function ($from, $to) {
- return strncmp(basename($from), '.', 1) !== 0;
- };
- }
- if (isset($options['afterCopy'])) {
- $opts['afterCopy'] = $options['afterCopy'];
- }
- FileHelper::copyDirectory($src, $dstDir, $opts);
- }
+ if (!is_dir($dstDir)) {
+ FileHelper::createDirectory($dstDir, $this->dirMode, true);
+ }
- return $this->_published[$path] = [$dstDir, $this->baseUrl . '/' . $dir];
- }
- }
+ if ($this->linkAssets) {
+ if (!is_file($dstFile)) {
+ symlink($src, $dstFile);
+ }
+ } elseif (@filemtime($dstFile) < @filemtime($src)) {
+ copy($src, $dstFile);
+ if ($this->fileMode !== null) {
+ @chmod($dstFile, $this->fileMode);
+ }
+ }
- /**
- * Returns the published path of a file path.
- * This method does not perform any publishing. It merely tells you
- * if the file or directory is published, where it will go.
- * @param string $path directory or file path being published
- * @return string the published file path. False if the file or directory does not exist
- */
- public function getPublishedPath($path)
- {
- $path = Yii::getAlias($path);
+ return $this->_published[$path] = [$dstFile, $this->baseUrl . "/$dir/$fileName"];
+ } else {
+ $dir = $this->hash($src . filemtime($src));
+ $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir;
+ if ($this->linkAssets) {
+ if (!is_dir($dstDir)) {
+ symlink($src, $dstDir);
+ }
+ } elseif (!is_dir($dstDir) || !empty($options['forceCopy'])) {
+ $opts = [
+ 'dirMode' => $this->dirMode,
+ 'fileMode' => $this->fileMode,
+ ];
+ if (isset($options['beforeCopy'])) {
+ $opts['beforeCopy'] = $options['beforeCopy'];
+ } else {
+ $opts['beforeCopy'] = function ($from, $to) {
+ return strncmp(basename($from), '.', 1) !== 0;
+ };
+ }
+ if (isset($options['afterCopy'])) {
+ $opts['afterCopy'] = $options['afterCopy'];
+ }
+ FileHelper::copyDirectory($src, $dstDir, $opts);
+ }
- if (isset($this->_published[$path])) {
- return $this->_published[$path][0];
- }
- if (is_string($path) && ($path = realpath($path)) !== false) {
- $base = $this->basePath . DIRECTORY_SEPARATOR;
- if (is_file($path)) {
- return $base . $this->hash(dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path);
- } else {
- return $base . $this->hash($path . filemtime($path));
- }
- } else {
- return false;
- }
- }
+ return $this->_published[$path] = [$dstDir, $this->baseUrl . '/' . $dir];
+ }
+ }
- /**
- * Returns the URL of a published file path.
- * This method does not perform any publishing. It merely tells you
- * if the file path is published, what the URL will be to access it.
- * @param string $path directory or file path being published
- * @return string the published URL for the file or directory. False if the file or directory does not exist.
- */
- public function getPublishedUrl($path)
- {
- $path = Yii::getAlias($path);
-
- if (isset($this->_published[$path])) {
- return $this->_published[$path][1];
- }
- if (is_string($path) && ($path = realpath($path)) !== false) {
- if (is_file($path)) {
- return $this->baseUrl . '/' . $this->hash(dirname($path) . filemtime($path)) . '/' . basename($path);
- } else {
- return $this->baseUrl . '/' . $this->hash($path . filemtime($path));
- }
- } else {
- return false;
- }
- }
+ /**
+ * Returns the published path of a file path.
+ * This method does not perform any publishing. It merely tells you
+ * if the file or directory is published, where it will go.
+ * @param string $path directory or file path being published
+ * @return string the published file path. False if the file or directory does not exist
+ */
+ public function getPublishedPath($path)
+ {
+ $path = Yii::getAlias($path);
- /**
- * Generate a CRC32 hash for the directory path. Collisions are higher
- * than MD5 but generates a much smaller hash string.
- * @param string $path string to be hashed.
- * @return string hashed string.
- */
- protected function hash($path)
- {
- return sprintf('%x', crc32($path . Yii::getVersion()));
- }
+ if (isset($this->_published[$path])) {
+ return $this->_published[$path][0];
+ }
+ if (is_string($path) && ($path = realpath($path)) !== false) {
+ $base = $this->basePath . DIRECTORY_SEPARATOR;
+ if (is_file($path)) {
+ return $base . $this->hash(dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path);
+ } else {
+ return $base . $this->hash($path . filemtime($path));
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the URL of a published file path.
+ * This method does not perform any publishing. It merely tells you
+ * if the file path is published, what the URL will be to access it.
+ * @param string $path directory or file path being published
+ * @return string the published URL for the file or directory. False if the file or directory does not exist.
+ */
+ public function getPublishedUrl($path)
+ {
+ $path = Yii::getAlias($path);
+
+ if (isset($this->_published[$path])) {
+ return $this->_published[$path][1];
+ }
+ if (is_string($path) && ($path = realpath($path)) !== false) {
+ if (is_file($path)) {
+ return $this->baseUrl . '/' . $this->hash(dirname($path) . filemtime($path)) . '/' . basename($path);
+ } else {
+ return $this->baseUrl . '/' . $this->hash($path . filemtime($path));
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Generate a CRC32 hash for the directory path. Collisions are higher
+ * than MD5 but generates a much smaller hash string.
+ * @param string $path string to be hashed.
+ * @return string hashed string.
+ */
+ protected function hash($path)
+ {
+ return sprintf('%x', crc32($path . Yii::getVersion()));
+ }
}
diff --git a/framework/web/BadRequestHttpException.php b/framework/web/BadRequestHttpException.php
index 6e596dab1aa..b5228295f19 100644
--- a/framework/web/BadRequestHttpException.php
+++ b/framework/web/BadRequestHttpException.php
@@ -21,14 +21,14 @@
*/
class BadRequestHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(400, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(400, $message, $code, $previous);
+ }
}
diff --git a/framework/web/CacheSession.php b/framework/web/CacheSession.php
index 7b4a98d4074..733fa27c8bf 100644
--- a/framework/web/CacheSession.php
+++ b/framework/web/CacheSession.php
@@ -38,81 +38,82 @@
*/
class CacheSession extends Session
{
- /**
- * @var Cache|string the cache object or the application component ID of the cache object.
- * The session data will be stored using this cache object.
- *
- * After the CacheSession object is created, if you want to change this property,
- * you should only assign it with a cache object.
- */
- public $cache = 'cache';
+ /**
+ * @var Cache|string the cache object or the application component ID of the cache object.
+ * The session data will be stored using this cache object.
+ *
+ * After the CacheSession object is created, if you want to change this property,
+ * you should only assign it with a cache object.
+ */
+ public $cache = 'cache';
- /**
- * Initializes the application component.
- */
- public function init()
- {
- if (is_string($this->cache)) {
- $this->cache = Yii::$app->getComponent($this->cache);
- }
- if (!$this->cache instanceof Cache) {
- throw new InvalidConfigException('CacheSession::cache must refer to the application component ID of a cache object.');
- }
- parent::init();
- }
+ /**
+ * Initializes the application component.
+ */
+ public function init()
+ {
+ if (is_string($this->cache)) {
+ $this->cache = Yii::$app->getComponent($this->cache);
+ }
+ if (!$this->cache instanceof Cache) {
+ throw new InvalidConfigException('CacheSession::cache must refer to the application component ID of a cache object.');
+ }
+ parent::init();
+ }
- /**
- * Returns a value indicating whether to use custom session storage.
- * This method overrides the parent implementation and always returns true.
- * @return boolean whether to use custom storage.
- */
- public function getUseCustomStorage()
- {
- return true;
- }
+ /**
+ * Returns a value indicating whether to use custom session storage.
+ * This method overrides the parent implementation and always returns true.
+ * @return boolean whether to use custom storage.
+ */
+ public function getUseCustomStorage()
+ {
+ return true;
+ }
- /**
- * Session read handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return string the session data
- */
- public function readSession($id)
- {
- $data = $this->cache->get($this->calculateKey($id));
- return $data === false ? '' : $data;
- }
+ /**
+ * Session read handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return string the session data
+ */
+ public function readSession($id)
+ {
+ $data = $this->cache->get($this->calculateKey($id));
- /**
- * Session write handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @param string $data session data
- * @return boolean whether session write is successful
- */
- public function writeSession($id, $data)
- {
- return $this->cache->set($this->calculateKey($id), $data, $this->getTimeout());
- }
+ return $data === false ? '' : $data;
+ }
- /**
- * Session destroy handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return boolean whether session is destroyed successfully
- */
- public function destroySession($id)
- {
- return $this->cache->delete($this->calculateKey($id));
- }
+ /**
+ * Session write handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @param string $data session data
+ * @return boolean whether session write is successful
+ */
+ public function writeSession($id, $data)
+ {
+ return $this->cache->set($this->calculateKey($id), $data, $this->getTimeout());
+ }
- /**
- * Generates a unique key used for storing session data in cache.
- * @param string $id session variable name
- * @return mixed a safe cache key associated with the session variable name
- */
- protected function calculateKey($id)
- {
- return [__CLASS__, $id];
- }
+ /**
+ * Session destroy handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return boolean whether session is destroyed successfully
+ */
+ public function destroySession($id)
+ {
+ return $this->cache->delete($this->calculateKey($id));
+ }
+
+ /**
+ * Generates a unique key used for storing session data in cache.
+ * @param string $id session variable name
+ * @return mixed a safe cache key associated with the session variable name
+ */
+ protected function calculateKey($id)
+ {
+ return [__CLASS__, $id];
+ }
}
diff --git a/framework/web/CompositeUrlRule.php b/framework/web/CompositeUrlRule.php
index 2382ec57f84..e2aa2e9a6bb 100644
--- a/framework/web/CompositeUrlRule.php
+++ b/framework/web/CompositeUrlRule.php
@@ -20,54 +20,56 @@
*/
abstract class CompositeUrlRule extends Object implements UrlRuleInterface
{
- /**
- * @var UrlRuleInterface[] the URL rules contained in this composite rule.
- * This property is set in [[init()]] by the return value of [[createRules()]].
- */
- protected $rules = [];
+ /**
+ * @var UrlRuleInterface[] the URL rules contained in this composite rule.
+ * This property is set in [[init()]] by the return value of [[createRules()]].
+ */
+ protected $rules = [];
+ /**
+ * Creates the URL rules that should be contained within this composite rule.
+ * @return UrlRuleInterface[] the URL rules
+ */
+ abstract protected function createRules();
- /**
- * Creates the URL rules that should be contained within this composite rule.
- * @return UrlRuleInterface[] the URL rules
- */
- abstract protected function createRules();
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ $this->rules = $this->createRules();
+ }
- /**
- * @inheritdoc
- */
- public function init()
- {
- parent::init();
- $this->rules = $this->createRules();
- }
+ /**
+ * @inheritdoc
+ */
+ public function parseRequest($manager, $request)
+ {
+ foreach ($this->rules as $rule) {
+ /** @var \yii\web\UrlRule $rule */
+ if (($result = $rule->parseRequest($manager, $request)) !== false) {
+ Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__);
- /**
- * @inheritdoc
- */
- public function parseRequest($manager, $request)
- {
- foreach ($this->rules as $rule) {
- /** @var \yii\web\UrlRule $rule */
- if (($result = $rule->parseRequest($manager, $request)) !== false) {
- Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__);
- return $result;
- }
- }
- return false;
- }
+ return $result;
+ }
+ }
- /**
- * @inheritdoc
- */
- public function createUrl($manager, $route, $params)
- {
- foreach ($this->rules as $rule) {
- /** @var \yii\web\UrlRule $rule */
- if (($url = $rule->createUrl($manager, $route, $params)) !== false) {
- return $url;
- }
- }
- return false;
- }
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createUrl($manager, $route, $params)
+ {
+ foreach ($this->rules as $rule) {
+ /** @var \yii\web\UrlRule $rule */
+ if (($url = $rule->createUrl($manager, $route, $params)) !== false) {
+ return $url;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/framework/web/ConflictHttpException.php b/framework/web/ConflictHttpException.php
index 6fa3f57bab4..75b63c51741 100644
--- a/framework/web/ConflictHttpException.php
+++ b/framework/web/ConflictHttpException.php
@@ -16,14 +16,14 @@
*/
class ConflictHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(409, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(409, $message, $code, $previous);
+ }
}
diff --git a/framework/web/Controller.php b/framework/web/Controller.php
index 3535c0b4b67..6bd7db5eadd 100644
--- a/framework/web/Controller.php
+++ b/framework/web/Controller.php
@@ -19,188 +19,188 @@
*/
class Controller extends \yii\base\Controller
{
- /**
- * @var boolean whether to enable CSRF validation for the actions in this controller.
- * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true.
- */
- public $enableCsrfValidation = true;
- /**
- * @var array the parameters bound to the current action.
- */
- public $actionParams = [];
+ /**
+ * @var boolean whether to enable CSRF validation for the actions in this controller.
+ * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true.
+ */
+ public $enableCsrfValidation = true;
+ /**
+ * @var array the parameters bound to the current action.
+ */
+ public $actionParams = [];
+ /**
+ * Renders a view in response to an AJAX request.
+ *
+ * This method is similar to [[renderPartial()]] except that it will inject into
+ * the rendering result with JS/CSS scripts and files which are registered with the view.
+ * For this reason, you should use this method instead of [[renderPartial()]] to render
+ * a view to respond to an AJAX request.
+ *
+ * @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ */
+ public function renderAjax($view, $params = [])
+ {
+ return $this->getView()->renderAjax($view, $params, $this);
+ }
- /**
- * Renders a view in response to an AJAX request.
- *
- * This method is similar to [[renderPartial()]] except that it will inject into
- * the rendering result with JS/CSS scripts and files which are registered with the view.
- * For this reason, you should use this method instead of [[renderPartial()]] to render
- * a view to respond to an AJAX request.
- *
- * @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
- * @param array $params the parameters (name-value pairs) that should be made available in the view.
- * @return string the rendering result.
- */
- public function renderAjax($view, $params = [])
- {
- return $this->getView()->renderAjax($view, $params, $this);
- }
+ /**
+ * Binds the parameters to the action.
+ * This method is invoked by [[Action]] when it begins to run with the given parameters.
+ * This method will check the parameter names that the action requires and return
+ * the provided parameters according to the requirement. If there is any missing parameter,
+ * an exception will be thrown.
+ * @param \yii\base\Action $action the action to be bound with parameters
+ * @param array $params the parameters to be bound to the action
+ * @return array the valid parameters that the action can run with.
+ * @throws HttpException if there are missing or invalid parameters.
+ */
+ public function bindActionParams($action, $params)
+ {
+ if ($action instanceof InlineAction) {
+ $method = new \ReflectionMethod($this, $action->actionMethod);
+ } else {
+ $method = new \ReflectionMethod($action, 'run');
+ }
- /**
- * Binds the parameters to the action.
- * This method is invoked by [[Action]] when it begins to run with the given parameters.
- * This method will check the parameter names that the action requires and return
- * the provided parameters according to the requirement. If there is any missing parameter,
- * an exception will be thrown.
- * @param \yii\base\Action $action the action to be bound with parameters
- * @param array $params the parameters to be bound to the action
- * @return array the valid parameters that the action can run with.
- * @throws HttpException if there are missing or invalid parameters.
- */
- public function bindActionParams($action, $params)
- {
- if ($action instanceof InlineAction) {
- $method = new \ReflectionMethod($this, $action->actionMethod);
- } else {
- $method = new \ReflectionMethod($action, 'run');
- }
+ $args = [];
+ $missing = [];
+ $actionParams = [];
+ foreach ($method->getParameters() as $param) {
+ $name = $param->getName();
+ if (array_key_exists($name, $params)) {
+ if ($param->isArray()) {
+ $args[] = $actionParams[$name] = is_array($params[$name]) ? $params[$name] : [$params[$name]];
+ } elseif (!is_array($params[$name])) {
+ $args[] = $actionParams[$name] = $params[$name];
+ } else {
+ throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [
+ 'param' => $name,
+ ]));
+ }
+ unset($params[$name]);
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $actionParams[$name] = $param->getDefaultValue();
+ } else {
+ $missing[] = $name;
+ }
+ }
- $args = [];
- $missing = [];
- $actionParams = [];
- foreach ($method->getParameters() as $param) {
- $name = $param->getName();
- if (array_key_exists($name, $params)) {
- if ($param->isArray()) {
- $args[] = $actionParams[$name] = is_array($params[$name]) ? $params[$name] : [$params[$name]];
- } elseif (!is_array($params[$name])) {
- $args[] = $actionParams[$name] = $params[$name];
- } else {
- throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [
- 'param' => $name,
- ]));
- }
- unset($params[$name]);
- } elseif ($param->isDefaultValueAvailable()) {
- $args[] = $actionParams[$name] = $param->getDefaultValue();
- } else {
- $missing[] = $name;
- }
- }
+ if (!empty($missing)) {
+ throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [
+ 'params' => implode(', ', $missing),
+ ]));
+ }
- if (!empty($missing)) {
- throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [
- 'params' => implode(', ', $missing),
- ]));
- }
+ $this->actionParams = $actionParams;
- $this->actionParams = $actionParams;
+ return $args;
+ }
- return $args;
- }
+ /**
+ * @inheritdoc
+ */
+ public function beforeAction($action)
+ {
+ if (parent::beforeAction($action)) {
+ if ($this->enableCsrfValidation && Yii::$app->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
+ throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
+ }
- /**
- * @inheritdoc
- */
- public function beforeAction($action)
- {
- if (parent::beforeAction($action)) {
- if ($this->enableCsrfValidation && Yii::$app->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
- throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
- }
- return true;
- } else {
- return false;
- }
- }
+ return true;
+ } else {
+ return false;
+ }
+ }
- /**
- * Redirects the browser to the specified URL.
- * This method is a shortcut to [[Response::redirect()]].
- *
- * You can use it in an action by returning the [[Response]] directly:
- *
- * ```php
- * // stop executing this action and redirect to login page
- * return $this->redirect(['login']);
- * ```
- *
- * @param string|array $url the URL to be redirected to. This can be in one of the following formats:
- *
- * - a string representing a URL (e.g. "http://example.com")
- * - a string representing a URL alias (e.g. "@example.com")
- * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`)
- * [[Url::to()]] will be used to convert the array into a URL.
- *
- * Any relative URL will be converted into an absolute one by prepending it with the host info
- * of the current request.
- *
- * @param integer $statusCode the HTTP status code. Defaults to 302.
- * See
- * for details about HTTP status code
- * @return Response the current response object
- */
- public function redirect($url, $statusCode = 302)
- {
- return Yii::$app->getResponse()->redirect(Url::to($url), $statusCode);
- }
+ /**
+ * Redirects the browser to the specified URL.
+ * This method is a shortcut to [[Response::redirect()]].
+ *
+ * You can use it in an action by returning the [[Response]] directly:
+ *
+ * ```php
+ * // stop executing this action and redirect to login page
+ * return $this->redirect(['login']);
+ * ```
+ *
+ * @param string|array $url the URL to be redirected to. This can be in one of the following formats:
+ *
+ * - a string representing a URL (e.g. "http://example.com")
+ * - a string representing a URL alias (e.g. "@example.com")
+ * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`)
+ * [[Url::to()]] will be used to convert the array into a URL.
+ *
+ * Any relative URL will be converted into an absolute one by prepending it with the host info
+ * of the current request.
+ *
+ * @param integer $statusCode the HTTP status code. Defaults to 302.
+ * See
+ * for details about HTTP status code
+ * @return Response the current response object
+ */
+ public function redirect($url, $statusCode = 302)
+ {
+ return Yii::$app->getResponse()->redirect(Url::to($url), $statusCode);
+ }
- /**
- * Redirects the browser to the home page.
- *
- * You can use this method in an action by returning the [[Response]] directly:
- *
- * ```php
- * // stop executing this action and redirect to home page
- * return $this->goHome();
- * ```
- *
- * @return Response the current response object
- */
- public function goHome()
- {
- return Yii::$app->getResponse()->redirect(Yii::$app->getHomeUrl());
- }
+ /**
+ * Redirects the browser to the home page.
+ *
+ * You can use this method in an action by returning the [[Response]] directly:
+ *
+ * ```php
+ * // stop executing this action and redirect to home page
+ * return $this->goHome();
+ * ```
+ *
+ * @return Response the current response object
+ */
+ public function goHome()
+ {
+ return Yii::$app->getResponse()->redirect(Yii::$app->getHomeUrl());
+ }
- /**
- * Redirects the browser to the last visited page.
- *
- * You can use this method in an action by returning the [[Response]] directly:
- *
- * ```php
- * // stop executing this action and redirect to last visited page
- * return $this->goBack();
- * ```
- *
- * @param string|array $defaultUrl the default return URL in case it was not set previously.
- * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
- * Please refer to [[User::setReturnUrl()]] on accepted format of the URL.
- * @return Response the current response object
- * @see User::getReturnUrl()
- */
- public function goBack($defaultUrl = null)
- {
- return Yii::$app->getResponse()->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl));
- }
+ /**
+ * Redirects the browser to the last visited page.
+ *
+ * You can use this method in an action by returning the [[Response]] directly:
+ *
+ * ```php
+ * // stop executing this action and redirect to last visited page
+ * return $this->goBack();
+ * ```
+ *
+ * @param string|array $defaultUrl the default return URL in case it was not set previously.
+ * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
+ * Please refer to [[User::setReturnUrl()]] on accepted format of the URL.
+ * @return Response the current response object
+ * @see User::getReturnUrl()
+ */
+ public function goBack($defaultUrl = null)
+ {
+ return Yii::$app->getResponse()->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl));
+ }
- /**
- * Refreshes the current page.
- * This method is a shortcut to [[Response::refresh()]].
- *
- * You can use it in an action by returning the [[Response]] directly:
- *
- * ```php
- * // stop executing this action and refresh the current page
- * return $this->refresh();
- * ```
- *
- * @param string $anchor the anchor that should be appended to the redirection URL.
- * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
- * @return Response the response object itself
- */
- public function refresh($anchor = '')
- {
- return Yii::$app->getResponse()->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
- }
+ /**
+ * Refreshes the current page.
+ * This method is a shortcut to [[Response::refresh()]].
+ *
+ * You can use it in an action by returning the [[Response]] directly:
+ *
+ * ```php
+ * // stop executing this action and refresh the current page
+ * return $this->refresh();
+ * ```
+ *
+ * @param string $anchor the anchor that should be appended to the redirection URL.
+ * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
+ * @return Response the response object itself
+ */
+ public function refresh($anchor = '')
+ {
+ return Yii::$app->getResponse()->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
+ }
}
diff --git a/framework/web/Cookie.php b/framework/web/Cookie.php
index 8cbb412dd7b..c3518722c22 100644
--- a/framework/web/Cookie.php
+++ b/framework/web/Cookie.php
@@ -15,51 +15,51 @@
*/
class Cookie extends \yii\base\Object
{
- /**
- * @var string name of the cookie
- */
- public $name;
- /**
- * @var string value of the cookie
- */
- public $value = '';
- /**
- * @var string domain of the cookie
- */
- public $domain = '';
- /**
- * @var integer the timestamp at which the cookie expires. This is the server timestamp.
- * Defaults to 0, meaning "until the browser is closed".
- */
- public $expire = 0;
- /**
- * @var string the path on the server in which the cookie will be available on. The default is '/'.
- */
- public $path = '/';
- /**
- * @var boolean whether cookie should be sent via secure connection
- */
- public $secure = false;
- /**
- * @var boolean whether the cookie should be accessible only through the HTTP protocol.
- * By setting this property to true, the cookie will not be accessible by scripting languages,
- * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
- */
- public $httpOnly = false;
+ /**
+ * @var string name of the cookie
+ */
+ public $name;
+ /**
+ * @var string value of the cookie
+ */
+ public $value = '';
+ /**
+ * @var string domain of the cookie
+ */
+ public $domain = '';
+ /**
+ * @var integer the timestamp at which the cookie expires. This is the server timestamp.
+ * Defaults to 0, meaning "until the browser is closed".
+ */
+ public $expire = 0;
+ /**
+ * @var string the path on the server in which the cookie will be available on. The default is '/'.
+ */
+ public $path = '/';
+ /**
+ * @var boolean whether cookie should be sent via secure connection
+ */
+ public $secure = false;
+ /**
+ * @var boolean whether the cookie should be accessible only through the HTTP protocol.
+ * By setting this property to true, the cookie will not be accessible by scripting languages,
+ * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
+ */
+ public $httpOnly = false;
- /**
- * Magic method to turn a cookie object into a string without having to explicitly access [[value]].
- *
- * ~~~
- * if (isset($request->cookies['name'])) {
- * $value = (string)$request->cookies['name'];
- * }
- * ~~~
- *
- * @return string The value of the cookie. If the value property is null, an empty string will be returned.
- */
- public function __toString()
- {
- return (string)$this->value;
- }
+ /**
+ * Magic method to turn a cookie object into a string without having to explicitly access [[value]].
+ *
+ * ~~~
+ * if (isset($request->cookies['name'])) {
+ * $value = (string) $request->cookies['name'];
+ * }
+ * ~~~
+ *
+ * @return string The value of the cookie. If the value property is null, an empty string will be returned.
+ */
+ public function __toString()
+ {
+ return (string) $this->value;
+ }
}
diff --git a/framework/web/CookieCollection.php b/framework/web/CookieCollection.php
index 475306c657d..cbdd5bea683 100644
--- a/framework/web/CookieCollection.php
+++ b/framework/web/CookieCollection.php
@@ -24,204 +24,204 @@
*/
class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
{
- /**
- * @var boolean whether this collection is read only.
- */
- public $readOnly = false;
-
- /**
- * @var Cookie[] the cookies in this collection (indexed by the cookie names)
- */
- private $_cookies = [];
-
- /**
- * Constructor.
- * @param array $cookies the cookies that this collection initially contains. This should be
- * an array of name-value pairs.s
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($cookies = [], $config = [])
- {
- $this->_cookies = $cookies;
- parent::__construct($config);
- }
-
- /**
- * Returns an iterator for traversing the cookies in the collection.
- * This method is required by the SPL interface `IteratorAggregate`.
- * It will be implicitly called when you use `foreach` to traverse the collection.
- * @return ArrayIterator an iterator for traversing the cookies in the collection.
- */
- public function getIterator()
- {
- return new ArrayIterator($this->_cookies);
- }
-
- /**
- * Returns the number of cookies in the collection.
- * This method is required by the SPL `Countable` interface.
- * It will be implicitly called when you use `count($collection)`.
- * @return integer the number of cookies in the collection.
- */
- public function count()
- {
- return $this->getCount();
- }
-
- /**
- * Returns the number of cookies in the collection.
- * @return integer the number of cookies in the collection.
- */
- public function getCount()
- {
- return count($this->_cookies);
- }
-
- /**
- * Returns the cookie with the specified name.
- * @param string $name the cookie name
- * @return Cookie the cookie with the specified name. Null if the named cookie does not exist.
- * @see getValue()
- */
- public function get($name)
- {
- return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null;
- }
-
- /**
- * Returns the value of the named cookie.
- * @param string $name the cookie name
- * @param mixed $defaultValue the value that should be returned when the named cookie does not exist.
- * @return mixed the value of the named cookie.
- * @see get()
- */
- public function getValue($name, $defaultValue = null)
- {
- return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue;
- }
-
- /**
- * Returns whether there is a cookie with the specified name.
- * @param string $name the cookie name
- * @return boolean whether the named cookie exists
- */
- public function has($name)
- {
- return isset($this->_cookies[$name]);
- }
-
- /**
- * Adds a cookie to the collection.
- * If there is already a cookie with the same name in the collection, it will be removed first.
- * @param Cookie $cookie the cookie to be added
- * @throws InvalidCallException if the cookie collection is read only
- */
- public function add($cookie)
- {
- if ($this->readOnly) {
- throw new InvalidCallException('The cookie collection is read only.');
- }
- $this->_cookies[$cookie->name] = $cookie;
- }
-
- /**
- * Removes a cookie.
- * If `$removeFromBrowser` is true, the cookie will be removed from the browser.
- * In this case, a cookie with outdated expiry will be added to the collection.
- * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
- * @param boolean $removeFromBrowser whether to remove the cookie from browser
- * @throws InvalidCallException if the cookie collection is read only
- */
- public function remove($cookie, $removeFromBrowser = true)
- {
- if ($this->readOnly) {
- throw new InvalidCallException('The cookie collection is read only.');
- }
- if ($cookie instanceof Cookie) {
- $cookie->expire = 1;
- $cookie->value = '';
- } else {
- $cookie = new Cookie([
- 'name' => $cookie,
- 'expire' => 1,
- ]);
- }
- if ($removeFromBrowser) {
- $this->_cookies[$cookie->name] = $cookie;
- } else {
- unset($this->_cookies[$cookie->name]);
- }
- }
-
- /**
- * Removes all cookies.
- * @throws InvalidCallException if the cookie collection is read only
- */
- public function removeAll()
- {
- if ($this->readOnly) {
- throw new InvalidCallException('The cookie collection is read only.');
- }
- $this->_cookies = [];
- }
-
- /**
- * Returns the collection as a PHP array.
- * @return array the array representation of the collection.
- * The array keys are cookie names, and the array values are the corresponding cookie objects.
- */
- public function toArray()
- {
- return $this->_cookies;
- }
-
- /**
- * Returns whether there is a cookie with the specified name.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `isset($collection[$name])`.
- * @param string $name the cookie name
- * @return boolean whether the named cookie exists
- */
- public function offsetExists($name)
- {
- return $this->has($name);
- }
-
- /**
- * Returns the cookie with the specified name.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `$cookie = $collection[$name];`.
- * This is equivalent to [[get()]].
- * @param string $name the cookie name
- * @return Cookie the cookie with the specified name, null if the named cookie does not exist.
- */
- public function offsetGet($name)
- {
- return $this->get($name);
- }
-
- /**
- * Adds the cookie to the collection.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `$collection[$name] = $cookie;`.
- * This is equivalent to [[add()]].
- * @param string $name the cookie name
- * @param Cookie $cookie the cookie to be added
- */
- public function offsetSet($name, $cookie)
- {
- $this->add($cookie);
- }
-
- /**
- * Removes the named cookie.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `unset($collection[$name])`.
- * This is equivalent to [[remove()]].
- * @param string $name the cookie name
- */
- public function offsetUnset($name)
- {
- $this->remove($name);
- }
+ /**
+ * @var boolean whether this collection is read only.
+ */
+ public $readOnly = false;
+
+ /**
+ * @var Cookie[] the cookies in this collection (indexed by the cookie names)
+ */
+ private $_cookies = [];
+
+ /**
+ * Constructor.
+ * @param array $cookies the cookies that this collection initially contains. This should be
+ * an array of name-value pairs.s
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($cookies = [], $config = [])
+ {
+ $this->_cookies = $cookies;
+ parent::__construct($config);
+ }
+
+ /**
+ * Returns an iterator for traversing the cookies in the collection.
+ * This method is required by the SPL interface `IteratorAggregate`.
+ * It will be implicitly called when you use `foreach` to traverse the collection.
+ * @return ArrayIterator an iterator for traversing the cookies in the collection.
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_cookies);
+ }
+
+ /**
+ * Returns the number of cookies in the collection.
+ * This method is required by the SPL `Countable` interface.
+ * It will be implicitly called when you use `count($collection)`.
+ * @return integer the number of cookies in the collection.
+ */
+ public function count()
+ {
+ return $this->getCount();
+ }
+
+ /**
+ * Returns the number of cookies in the collection.
+ * @return integer the number of cookies in the collection.
+ */
+ public function getCount()
+ {
+ return count($this->_cookies);
+ }
+
+ /**
+ * Returns the cookie with the specified name.
+ * @param string $name the cookie name
+ * @return Cookie the cookie with the specified name. Null if the named cookie does not exist.
+ * @see getValue()
+ */
+ public function get($name)
+ {
+ return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null;
+ }
+
+ /**
+ * Returns the value of the named cookie.
+ * @param string $name the cookie name
+ * @param mixed $defaultValue the value that should be returned when the named cookie does not exist.
+ * @return mixed the value of the named cookie.
+ * @see get()
+ */
+ public function getValue($name, $defaultValue = null)
+ {
+ return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue;
+ }
+
+ /**
+ * Returns whether there is a cookie with the specified name.
+ * @param string $name the cookie name
+ * @return boolean whether the named cookie exists
+ */
+ public function has($name)
+ {
+ return isset($this->_cookies[$name]);
+ }
+
+ /**
+ * Adds a cookie to the collection.
+ * If there is already a cookie with the same name in the collection, it will be removed first.
+ * @param Cookie $cookie the cookie to be added
+ * @throws InvalidCallException if the cookie collection is read only
+ */
+ public function add($cookie)
+ {
+ if ($this->readOnly) {
+ throw new InvalidCallException('The cookie collection is read only.');
+ }
+ $this->_cookies[$cookie->name] = $cookie;
+ }
+
+ /**
+ * Removes a cookie.
+ * If `$removeFromBrowser` is true, the cookie will be removed from the browser.
+ * In this case, a cookie with outdated expiry will be added to the collection.
+ * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
+ * @param boolean $removeFromBrowser whether to remove the cookie from browser
+ * @throws InvalidCallException if the cookie collection is read only
+ */
+ public function remove($cookie, $removeFromBrowser = true)
+ {
+ if ($this->readOnly) {
+ throw new InvalidCallException('The cookie collection is read only.');
+ }
+ if ($cookie instanceof Cookie) {
+ $cookie->expire = 1;
+ $cookie->value = '';
+ } else {
+ $cookie = new Cookie([
+ 'name' => $cookie,
+ 'expire' => 1,
+ ]);
+ }
+ if ($removeFromBrowser) {
+ $this->_cookies[$cookie->name] = $cookie;
+ } else {
+ unset($this->_cookies[$cookie->name]);
+ }
+ }
+
+ /**
+ * Removes all cookies.
+ * @throws InvalidCallException if the cookie collection is read only
+ */
+ public function removeAll()
+ {
+ if ($this->readOnly) {
+ throw new InvalidCallException('The cookie collection is read only.');
+ }
+ $this->_cookies = [];
+ }
+
+ /**
+ * Returns the collection as a PHP array.
+ * @return array the array representation of the collection.
+ * The array keys are cookie names, and the array values are the corresponding cookie objects.
+ */
+ public function toArray()
+ {
+ return $this->_cookies;
+ }
+
+ /**
+ * Returns whether there is a cookie with the specified name.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `isset($collection[$name])`.
+ * @param string $name the cookie name
+ * @return boolean whether the named cookie exists
+ */
+ public function offsetExists($name)
+ {
+ return $this->has($name);
+ }
+
+ /**
+ * Returns the cookie with the specified name.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$cookie = $collection[$name];`.
+ * This is equivalent to [[get()]].
+ * @param string $name the cookie name
+ * @return Cookie the cookie with the specified name, null if the named cookie does not exist.
+ */
+ public function offsetGet($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Adds the cookie to the collection.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$collection[$name] = $cookie;`.
+ * This is equivalent to [[add()]].
+ * @param string $name the cookie name
+ * @param Cookie $cookie the cookie to be added
+ */
+ public function offsetSet($name, $cookie)
+ {
+ $this->add($cookie);
+ }
+
+ /**
+ * Removes the named cookie.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `unset($collection[$name])`.
+ * This is equivalent to [[remove()]].
+ * @param string $name the cookie name
+ */
+ public function offsetUnset($name)
+ {
+ $this->remove($name);
+ }
}
diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php
index 2b0b0e716df..798f0e99488 100644
--- a/framework/web/DbSession.php
+++ b/framework/web/DbSession.php
@@ -36,189 +36,193 @@
*/
class DbSession extends Session
{
- /**
- * @var Connection|string the DB connection object or the application component ID of the DB connection.
- * After the DbSession object is created, if you want to change this property, you should only assign it
- * with a DB connection object.
- */
- public $db = 'db';
- /**
- * @var string the name of the DB table that stores the session data.
- * The table should be pre-created as follows:
- *
- * ~~~
- * CREATE TABLE tbl_session
- * (
- * id CHAR(40) NOT NULL PRIMARY KEY,
- * expire INTEGER,
- * data BLOB
- * )
- * ~~~
- *
- * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
- * that can be used for some popular DBMS:
- *
- * - MySQL: LONGBLOB
- * - PostgreSQL: BYTEA
- * - MSSQL: BLOB
- *
- * When using DbSession in a production server, we recommend you create a DB index for the 'expire'
- * column in the session table to improve the performance.
- */
- public $sessionTable = '{{%session}}';
-
- /**
- * Initializes the DbSession component.
- * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
- * @throws InvalidConfigException if [[db]] is invalid.
- */
- public function init()
- {
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!$this->db instanceof Connection) {
- throw new InvalidConfigException("DbSession::db must be either a DB connection instance or the application component ID of a DB connection.");
- }
- parent::init();
- }
-
- /**
- * Returns a value indicating whether to use custom session storage.
- * This method overrides the parent implementation and always returns true.
- * @return boolean whether to use custom storage.
- */
- public function getUseCustomStorage()
- {
- return true;
- }
-
- /**
- * Updates the current session ID with a newly generated one .
- * Please refer to for more details.
- * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
- */
- public function regenerateID($deleteOldSession = false)
- {
- $oldID = session_id();
-
- // if no session is started, there is nothing to regenerate
- if (empty($oldID)) {
- return;
- }
-
- parent::regenerateID(false);
- $newID = session_id();
-
- $query = new Query;
- $row = $query->from($this->sessionTable)
- ->where(['id' => $oldID])
- ->createCommand($this->db)
- ->queryOne();
- if ($row !== false) {
- if ($deleteOldSession) {
- $this->db->createCommand()
- ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID])
- ->execute();
- } else {
- $row['id'] = $newID;
- $this->db->createCommand()
- ->insert($this->sessionTable, $row)
- ->execute();
- }
- } else {
- // shouldn't reach here normally
- $this->db->createCommand()
- ->insert($this->sessionTable, [
- 'id' => $newID,
- 'expire' => time() + $this->getTimeout(),
- ])->execute();
- }
- }
-
- /**
- * Session read handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return string the session data
- */
- public function readSession($id)
- {
- $query = new Query;
- $data = $query->select(['data'])
- ->from($this->sessionTable)
- ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id])
- ->createCommand($this->db)
- ->queryScalar();
- return $data === false ? '' : $data;
- }
-
- /**
- * Session write handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @param string $data session data
- * @return boolean whether session write is successful
- */
- public function writeSession($id, $data)
- {
- // exception must be caught in session write handler
- // http://us.php.net/manual/en/function.session-set-save-handler.php
- try {
- $expire = time() + $this->getTimeout();
- $query = new Query;
- $exists = $query->select(['id'])
- ->from($this->sessionTable)
- ->where(['id' => $id])
- ->createCommand($this->db)
- ->queryScalar();
- if ($exists === false) {
- $this->db->createCommand()
- ->insert($this->sessionTable, [
- 'id' => $id,
- 'data' => $data,
- 'expire' => $expire,
- ])->execute();
- } else {
- $this->db->createCommand()
- ->update($this->sessionTable, ['data' => $data, 'expire' => $expire], ['id' => $id])
- ->execute();
- }
- } catch (\Exception $e) {
- if (YII_DEBUG) {
- echo $e->getMessage();
- }
- // it is too late to log an error message here
- return false;
- }
- return true;
- }
-
- /**
- * Session destroy handler.
- * Do not call this method directly.
- * @param string $id session ID
- * @return boolean whether session is destroyed successfully
- */
- public function destroySession($id)
- {
- $this->db->createCommand()
- ->delete($this->sessionTable, ['id' => $id])
- ->execute();
- return true;
- }
-
- /**
- * Session GC (garbage collection) handler.
- * Do not call this method directly.
- * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
- * @return boolean whether session is GCed successfully
- */
- public function gcSession($maxLifetime)
- {
- $this->db->createCommand()
- ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()])
- ->execute();
- return true;
- }
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ * After the DbSession object is created, if you want to change this property, you should only assign it
+ * with a DB connection object.
+ */
+ public $db = 'db';
+ /**
+ * @var string the name of the DB table that stores the session data.
+ * The table should be pre-created as follows:
+ *
+ * ~~~
+ * CREATE TABLE tbl_session
+ * (
+ * id CHAR(40) NOT NULL PRIMARY KEY,
+ * expire INTEGER,
+ * data BLOB
+ * )
+ * ~~~
+ *
+ * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
+ * that can be used for some popular DBMS:
+ *
+ * - MySQL: LONGBLOB
+ * - PostgreSQL: BYTEA
+ * - MSSQL: BLOB
+ *
+ * When using DbSession in a production server, we recommend you create a DB index for the 'expire'
+ * column in the session table to improve the performance.
+ */
+ public $sessionTable = '{{%session}}';
+
+ /**
+ * Initializes the DbSession component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException("DbSession::db must be either a DB connection instance or the application component ID of a DB connection.");
+ }
+ parent::init();
+ }
+
+ /**
+ * Returns a value indicating whether to use custom session storage.
+ * This method overrides the parent implementation and always returns true.
+ * @return boolean whether to use custom storage.
+ */
+ public function getUseCustomStorage()
+ {
+ return true;
+ }
+
+ /**
+ * Updates the current session ID with a newly generated one .
+ * Please refer to for more details.
+ * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
+ */
+ public function regenerateID($deleteOldSession = false)
+ {
+ $oldID = session_id();
+
+ // if no session is started, there is nothing to regenerate
+ if (empty($oldID)) {
+ return;
+ }
+
+ parent::regenerateID(false);
+ $newID = session_id();
+
+ $query = new Query;
+ $row = $query->from($this->sessionTable)
+ ->where(['id' => $oldID])
+ ->createCommand($this->db)
+ ->queryOne();
+ if ($row !== false) {
+ if ($deleteOldSession) {
+ $this->db->createCommand()
+ ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID])
+ ->execute();
+ } else {
+ $row['id'] = $newID;
+ $this->db->createCommand()
+ ->insert($this->sessionTable, $row)
+ ->execute();
+ }
+ } else {
+ // shouldn't reach here normally
+ $this->db->createCommand()
+ ->insert($this->sessionTable, [
+ 'id' => $newID,
+ 'expire' => time() + $this->getTimeout(),
+ ])->execute();
+ }
+ }
+
+ /**
+ * Session read handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return string the session data
+ */
+ public function readSession($id)
+ {
+ $query = new Query;
+ $data = $query->select(['data'])
+ ->from($this->sessionTable)
+ ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id])
+ ->createCommand($this->db)
+ ->queryScalar();
+
+ return $data === false ? '' : $data;
+ }
+
+ /**
+ * Session write handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @param string $data session data
+ * @return boolean whether session write is successful
+ */
+ public function writeSession($id, $data)
+ {
+ // exception must be caught in session write handler
+ // http://us.php.net/manual/en/function.session-set-save-handler.php
+ try {
+ $expire = time() + $this->getTimeout();
+ $query = new Query;
+ $exists = $query->select(['id'])
+ ->from($this->sessionTable)
+ ->where(['id' => $id])
+ ->createCommand($this->db)
+ ->queryScalar();
+ if ($exists === false) {
+ $this->db->createCommand()
+ ->insert($this->sessionTable, [
+ 'id' => $id,
+ 'data' => $data,
+ 'expire' => $expire,
+ ])->execute();
+ } else {
+ $this->db->createCommand()
+ ->update($this->sessionTable, ['data' => $data, 'expire' => $expire], ['id' => $id])
+ ->execute();
+ }
+ } catch (\Exception $e) {
+ if (YII_DEBUG) {
+ echo $e->getMessage();
+ }
+ // it is too late to log an error message here
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Session destroy handler.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return boolean whether session is destroyed successfully
+ */
+ public function destroySession($id)
+ {
+ $this->db->createCommand()
+ ->delete($this->sessionTable, ['id' => $id])
+ ->execute();
+
+ return true;
+ }
+
+ /**
+ * Session GC (garbage collection) handler.
+ * Do not call this method directly.
+ * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
+ * @return boolean whether session is GCed successfully
+ */
+ public function gcSession($maxLifetime)
+ {
+ $this->db->createCommand()
+ ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()])
+ ->execute();
+
+ return true;
+ }
}
diff --git a/framework/web/ErrorAction.php b/framework/web/ErrorAction.php
index 95f17bee205..6910e97d690 100644
--- a/framework/web/ErrorAction.php
+++ b/framework/web/ErrorAction.php
@@ -49,58 +49,57 @@
*/
class ErrorAction extends Action
{
- /**
- * @var string the view file to be rendered. If not set, it will take the value of [[id]].
- * That means, if you name the action as "error" in "SiteController", then the view name
- * would be "error", and the corresponding view file would be "views/site/error.php".
- */
- public $view;
- /**
- * @var string the name of the error when the exception name cannot be determined.
- * Defaults to "Error".
- */
- public $defaultName;
- /**
- * @var string the message to be displayed when the exception message contains sensitive information.
- * Defaults to "An internal server error occurred.".
- */
- public $defaultMessage;
+ /**
+ * @var string the view file to be rendered. If not set, it will take the value of [[id]].
+ * That means, if you name the action as "error" in "SiteController", then the view name
+ * would be "error", and the corresponding view file would be "views/site/error.php".
+ */
+ public $view;
+ /**
+ * @var string the name of the error when the exception name cannot be determined.
+ * Defaults to "Error".
+ */
+ public $defaultName;
+ /**
+ * @var string the message to be displayed when the exception message contains sensitive information.
+ * Defaults to "An internal server error occurred.".
+ */
+ public $defaultMessage;
+ public function run()
+ {
+ if (($exception = Yii::$app->exception) === null) {
+ return '';
+ }
- public function run()
- {
- if (($exception = Yii::$app->exception) === null) {
- return '';
- }
+ if ($exception instanceof HttpException) {
+ $code = $exception->statusCode;
+ } else {
+ $code = $exception->getCode();
+ }
+ if ($exception instanceof Exception) {
+ $name = $exception->getName();
+ } else {
+ $name = $this->defaultName ?: Yii::t('yii', 'Error');
+ }
+ if ($code) {
+ $name .= " (#$code)";
+ }
- if ($exception instanceof HttpException) {
- $code = $exception->statusCode;
- } else {
- $code = $exception->getCode();
- }
- if ($exception instanceof Exception) {
- $name = $exception->getName();
- } else {
- $name = $this->defaultName ?: Yii::t('yii', 'Error');
- }
- if ($code) {
- $name .= " (#$code)";
- }
+ if ($exception instanceof UserException) {
+ $message = $exception->getMessage();
+ } else {
+ $message = $this->defaultMessage ?: Yii::t('yii', 'An internal server error occurred.');
+ }
- if ($exception instanceof UserException) {
- $message = $exception->getMessage();
- } else {
- $message = $this->defaultMessage ?: Yii::t('yii', 'An internal server error occurred.');
- }
-
- if (Yii::$app->getRequest()->getIsAjax()) {
- return "$name: $message";
- } else {
- return $this->controller->render($this->view ?: $this->id, [
- 'name' => $name,
- 'message' => $message,
- 'exception' => $exception,
- ]);
- }
- }
+ if (Yii::$app->getRequest()->getIsAjax()) {
+ return "$name: $message";
+ } else {
+ return $this->controller->render($this->view ?: $this->id, [
+ 'name' => $name,
+ 'message' => $message,
+ 'exception' => $exception,
+ ]);
+ }
+ }
}
diff --git a/framework/web/ForbiddenHttpException.php b/framework/web/ForbiddenHttpException.php
index e96c2252933..f9388102c75 100644
--- a/framework/web/ForbiddenHttpException.php
+++ b/framework/web/ForbiddenHttpException.php
@@ -22,14 +22,14 @@
*/
class ForbiddenHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(403, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(403, $message, $code, $previous);
+ }
}
diff --git a/framework/web/GoneHttpException.php b/framework/web/GoneHttpException.php
index b78aa01b595..358a0f1fe72 100644
--- a/framework/web/GoneHttpException.php
+++ b/framework/web/GoneHttpException.php
@@ -21,14 +21,14 @@
*/
class GoneHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(410, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(410, $message, $code, $previous);
+ }
}
diff --git a/framework/web/HeaderCollection.php b/framework/web/HeaderCollection.php
index e8e4f9c4710..781eb939200 100644
--- a/framework/web/HeaderCollection.php
+++ b/framework/web/HeaderCollection.php
@@ -23,199 +23,204 @@
*/
class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
{
- /**
- * @var array the headers in this collection (indexed by the header names)
- */
- private $_headers = [];
-
- /**
- * Returns an iterator for traversing the headers in the collection.
- * This method is required by the SPL interface `IteratorAggregate`.
- * It will be implicitly called when you use `foreach` to traverse the collection.
- * @return ArrayIterator an iterator for traversing the headers in the collection.
- */
- public function getIterator()
- {
- return new ArrayIterator($this->_headers);
- }
-
- /**
- * Returns the number of headers in the collection.
- * This method is required by the SPL `Countable` interface.
- * It will be implicitly called when you use `count($collection)`.
- * @return integer the number of headers in the collection.
- */
- public function count()
- {
- return $this->getCount();
- }
-
- /**
- * Returns the number of headers in the collection.
- * @return integer the number of headers in the collection.
- */
- public function getCount()
- {
- return count($this->_headers);
- }
-
- /**
- * Returns the named header(s).
- * @param string $name the name of the header to return
- * @param mixed $default the value to return in case the named header does not exist
- * @param boolean $first whether to only return the first header of the specified name.
- * If false, all headers of the specified name will be returned.
- * @return string|array the named header(s). If `$first` is true, a string will be returned;
- * If `$first` is false, an array will be returned.
- */
- public function get($name, $default = null, $first = true)
- {
- $name = strtolower($name);
- if (isset($this->_headers[$name])) {
- return $first ? reset($this->_headers[$name]) : $this->_headers[$name];
- } else {
- return $default;
- }
- }
-
- /**
- * Adds a new header.
- * If there is already a header with the same name, it will be replaced.
- * @param string $name the name of the header
- * @param string $value the value of the header
- * @return static the collection object itself
- */
- public function set($name, $value = '')
- {
- $name = strtolower($name);
- $this->_headers[$name] = (array)$value;
- return $this;
- }
-
- /**
- * Adds a new header.
- * If there is already a header with the same name, the new one will
- * be appended to it instead of replacing it.
- * @param string $name the name of the header
- * @param string $value the value of the header
- * @return static the collection object itself
- */
- public function add($name, $value)
- {
- $name = strtolower($name);
- $this->_headers[$name][] = $value;
- return $this;
- }
-
- /**
- * Sets a new header only if it does not exist yet.
- * If there is already a header with the same name, the new one will be ignored.
- * @param string $name the name of the header
- * @param string $value the value of the header
- * @return static the collection object itself
- */
- public function setDefault($name, $value)
- {
- $name = strtolower($name);
- if (empty($this->_headers[$name])) {
- $this->_headers[$name][] = $value;
- }
- return $this;
- }
-
- /**
- * Returns a value indicating whether the named header exists.
- * @param string $name the name of the header
- * @return boolean whether the named header exists
- */
- public function has($name)
- {
- $name = strtolower($name);
- return isset($this->_headers[$name]);
- }
-
- /**
- * Removes a header.
- * @param string $name the name of the header to be removed.
- * @return string the value of the removed header. Null is returned if the header does not exist.
- */
- public function remove($name)
- {
- $name = strtolower($name);
- if (isset($this->_headers[$name])) {
- $value = $this->_headers[$name];
- unset($this->_headers[$name]);
- return $value;
- } else {
- return null;
- }
- }
-
- /**
- * Removes all headers.
- */
- public function removeAll()
- {
- $this->_headers = [];
- }
-
- /**
- * Returns the collection as a PHP array.
- * @return array the array representation of the collection.
- * The array keys are header names, and the array values are the corresponding header values.
- */
- public function toArray()
- {
- return $this->_headers;
- }
-
- /**
- * Returns whether there is a header with the specified name.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `isset($collection[$name])`.
- * @param string $name the header name
- * @return boolean whether the named header exists
- */
- public function offsetExists($name)
- {
- return $this->has($name);
- }
-
- /**
- * Returns the header with the specified name.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `$header = $collection[$name];`.
- * This is equivalent to [[get()]].
- * @param string $name the header name
- * @return string the header value with the specified name, null if the named header does not exist.
- */
- public function offsetGet($name)
- {
- return $this->get($name);
- }
-
- /**
- * Adds the header to the collection.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `$collection[$name] = $header;`.
- * This is equivalent to [[add()]].
- * @param string $name the header name
- * @param string $value the header value to be added
- */
- public function offsetSet($name, $value)
- {
- $this->set($name, $value);
- }
-
- /**
- * Removes the named header.
- * This method is required by the SPL interface `ArrayAccess`.
- * It is implicitly called when you use something like `unset($collection[$name])`.
- * This is equivalent to [[remove()]].
- * @param string $name the header name
- */
- public function offsetUnset($name)
- {
- $this->remove($name);
- }
+ /**
+ * @var array the headers in this collection (indexed by the header names)
+ */
+ private $_headers = [];
+
+ /**
+ * Returns an iterator for traversing the headers in the collection.
+ * This method is required by the SPL interface `IteratorAggregate`.
+ * It will be implicitly called when you use `foreach` to traverse the collection.
+ * @return ArrayIterator an iterator for traversing the headers in the collection.
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_headers);
+ }
+
+ /**
+ * Returns the number of headers in the collection.
+ * This method is required by the SPL `Countable` interface.
+ * It will be implicitly called when you use `count($collection)`.
+ * @return integer the number of headers in the collection.
+ */
+ public function count()
+ {
+ return $this->getCount();
+ }
+
+ /**
+ * Returns the number of headers in the collection.
+ * @return integer the number of headers in the collection.
+ */
+ public function getCount()
+ {
+ return count($this->_headers);
+ }
+
+ /**
+ * Returns the named header(s).
+ * @param string $name the name of the header to return
+ * @param mixed $default the value to return in case the named header does not exist
+ * @param boolean $first whether to only return the first header of the specified name.
+ * If false, all headers of the specified name will be returned.
+ * @return string|array the named header(s). If `$first` is true, a string will be returned;
+ * If `$first` is false, an array will be returned.
+ */
+ public function get($name, $default = null, $first = true)
+ {
+ $name = strtolower($name);
+ if (isset($this->_headers[$name])) {
+ return $first ? reset($this->_headers[$name]) : $this->_headers[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Adds a new header.
+ * If there is already a header with the same name, it will be replaced.
+ * @param string $name the name of the header
+ * @param string $value the value of the header
+ * @return static the collection object itself
+ */
+ public function set($name, $value = '')
+ {
+ $name = strtolower($name);
+ $this->_headers[$name] = (array) $value;
+
+ return $this;
+ }
+
+ /**
+ * Adds a new header.
+ * If there is already a header with the same name, the new one will
+ * be appended to it instead of replacing it.
+ * @param string $name the name of the header
+ * @param string $value the value of the header
+ * @return static the collection object itself
+ */
+ public function add($name, $value)
+ {
+ $name = strtolower($name);
+ $this->_headers[$name][] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets a new header only if it does not exist yet.
+ * If there is already a header with the same name, the new one will be ignored.
+ * @param string $name the name of the header
+ * @param string $value the value of the header
+ * @return static the collection object itself
+ */
+ public function setDefault($name, $value)
+ {
+ $name = strtolower($name);
+ if (empty($this->_headers[$name])) {
+ $this->_headers[$name][] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns a value indicating whether the named header exists.
+ * @param string $name the name of the header
+ * @return boolean whether the named header exists
+ */
+ public function has($name)
+ {
+ $name = strtolower($name);
+
+ return isset($this->_headers[$name]);
+ }
+
+ /**
+ * Removes a header.
+ * @param string $name the name of the header to be removed.
+ * @return string the value of the removed header. Null is returned if the header does not exist.
+ */
+ public function remove($name)
+ {
+ $name = strtolower($name);
+ if (isset($this->_headers[$name])) {
+ $value = $this->_headers[$name];
+ unset($this->_headers[$name]);
+
+ return $value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Removes all headers.
+ */
+ public function removeAll()
+ {
+ $this->_headers = [];
+ }
+
+ /**
+ * Returns the collection as a PHP array.
+ * @return array the array representation of the collection.
+ * The array keys are header names, and the array values are the corresponding header values.
+ */
+ public function toArray()
+ {
+ return $this->_headers;
+ }
+
+ /**
+ * Returns whether there is a header with the specified name.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `isset($collection[$name])`.
+ * @param string $name the header name
+ * @return boolean whether the named header exists
+ */
+ public function offsetExists($name)
+ {
+ return $this->has($name);
+ }
+
+ /**
+ * Returns the header with the specified name.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$header = $collection[$name];`.
+ * This is equivalent to [[get()]].
+ * @param string $name the header name
+ * @return string the header value with the specified name, null if the named header does not exist.
+ */
+ public function offsetGet($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Adds the header to the collection.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$collection[$name] = $header;`.
+ * This is equivalent to [[add()]].
+ * @param string $name the header name
+ * @param string $value the header value to be added
+ */
+ public function offsetSet($name, $value)
+ {
+ $this->set($name, $value);
+ }
+
+ /**
+ * Removes the named header.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `unset($collection[$name])`.
+ * This is equivalent to [[remove()]].
+ * @param string $name the header name
+ */
+ public function offsetUnset($name)
+ {
+ $this->remove($name);
+ }
}
diff --git a/framework/web/HttpCache.php b/framework/web/HttpCache.php
index fd579981cff..a319ebb1692 100644
--- a/framework/web/HttpCache.php
+++ b/framework/web/HttpCache.php
@@ -45,116 +45,118 @@
*/
class HttpCache extends ActionFilter
{
- /**
- * @var callable a PHP callback that returns the UNIX timestamp of the last modification time.
- * The callback's signature should be:
- *
- * ~~~
- * function ($action, $params)
- * ~~~
- *
- * where `$action` is the [[Action]] object that this filter is currently handling;
- * `$params` takes the value of [[params]]. The callback should return a UNIX timestamp.
- */
- public $lastModified;
- /**
- * @var callable a PHP callback that generates the Etag seed string.
- * The callback's signature should be:
- *
- * ~~~
- * function ($action, $params)
- * ~~~
- *
- * where `$action` is the [[Action]] object that this filter is currently handling;
- * `$params` takes the value of [[params]]. The callback should return a string serving
- * as the seed for generating an Etag.
- */
- public $etagSeed;
- /**
- * @var mixed additional parameters that should be passed to the [[lastModified]] and [[etagSeed]] callbacks.
- */
- public $params;
- /**
- * @var string HTTP cache control header. If null, the header will not be sent.
- */
- public $cacheControlHeader = 'max-age=3600, public';
+ /**
+ * @var callable a PHP callback that returns the UNIX timestamp of the last modification time.
+ * The callback's signature should be:
+ *
+ * ~~~
+ * function ($action, $params)
+ * ~~~
+ *
+ * where `$action` is the [[Action]] object that this filter is currently handling;
+ * `$params` takes the value of [[params]]. The callback should return a UNIX timestamp.
+ */
+ public $lastModified;
+ /**
+ * @var callable a PHP callback that generates the Etag seed string.
+ * The callback's signature should be:
+ *
+ * ~~~
+ * function ($action, $params)
+ * ~~~
+ *
+ * where `$action` is the [[Action]] object that this filter is currently handling;
+ * `$params` takes the value of [[params]]. The callback should return a string serving
+ * as the seed for generating an Etag.
+ */
+ public $etagSeed;
+ /**
+ * @var mixed additional parameters that should be passed to the [[lastModified]] and [[etagSeed]] callbacks.
+ */
+ public $params;
+ /**
+ * @var string HTTP cache control header. If null, the header will not be sent.
+ */
+ public $cacheControlHeader = 'max-age=3600, public';
- /**
- * This method is invoked right before an action is to be executed (after all possible filters.)
- * You may override this method to do last-minute preparation for the action.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- $verb = Yii::$app->getRequest()->getMethod();
- if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
- return true;
- }
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $verb = Yii::$app->getRequest()->getMethod();
+ if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
+ return true;
+ }
- $lastModified = $etag = null;
- if ($this->lastModified !== null) {
- $lastModified = call_user_func($this->lastModified, $action, $this->params);
- }
- if ($this->etagSeed !== null) {
- $seed = call_user_func($this->etagSeed, $action, $this->params);
- $etag = $this->generateEtag($seed);
- }
+ $lastModified = $etag = null;
+ if ($this->lastModified !== null) {
+ $lastModified = call_user_func($this->lastModified, $action, $this->params);
+ }
+ if ($this->etagSeed !== null) {
+ $seed = call_user_func($this->etagSeed, $action, $this->params);
+ $etag = $this->generateEtag($seed);
+ }
- $this->sendCacheControlHeader();
- $response = Yii::$app->getResponse();
- if ($etag !== null) {
- $response->getHeaders()->set('Etag', $etag);
- }
+ $this->sendCacheControlHeader();
+ $response = Yii::$app->getResponse();
+ if ($etag !== null) {
+ $response->getHeaders()->set('Etag', $etag);
+ }
- if ($this->validateCache($lastModified, $etag)) {
- $response->setStatusCode(304);
- return false;
- }
+ if ($this->validateCache($lastModified, $etag)) {
+ $response->setStatusCode(304);
- if ($lastModified !== null) {
- $response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
- }
- return true;
- }
+ return false;
+ }
- /**
- * Validates if the HTTP cache contains valid content.
- * @param integer $lastModified the calculated Last-Modified value in terms of a UNIX timestamp.
- * If null, the Last-Modified header will not be validated.
- * @param string $etag the calculated ETag value. If null, the ETag header will not be validated.
- * @return boolean whether the HTTP cache is still valid.
- */
- protected function validateCache($lastModified, $etag)
- {
- if ($lastModified !== null && (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < $lastModified)) {
- return false;
- } else {
- return $etag === null || isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag;
- }
- }
+ if ($lastModified !== null) {
+ $response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
+ }
- /**
- * Sends the cache control header to the client
- * @see cacheControl
- */
- protected function sendCacheControlHeader()
- {
- session_cache_limiter('public');
- $headers = Yii::$app->getResponse()->getHeaders();
- $headers->set('Pragma');
- if ($this->cacheControlHeader !== null) {
- $headers->set('Cache-Control', $this->cacheControlHeader);
- }
- }
+ return true;
+ }
- /**
- * Generates an Etag from the given seed string.
- * @param string $seed Seed for the ETag
- * @return string the generated Etag
- */
- protected function generateEtag($seed)
- {
- return '"' . base64_encode(sha1($seed, true)) . '"';
- }
+ /**
+ * Validates if the HTTP cache contains valid content.
+ * @param integer $lastModified the calculated Last-Modified value in terms of a UNIX timestamp.
+ * If null, the Last-Modified header will not be validated.
+ * @param string $etag the calculated ETag value. If null, the ETag header will not be validated.
+ * @return boolean whether the HTTP cache is still valid.
+ */
+ protected function validateCache($lastModified, $etag)
+ {
+ if ($lastModified !== null && (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < $lastModified)) {
+ return false;
+ } else {
+ return $etag === null || isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag;
+ }
+ }
+
+ /**
+ * Sends the cache control header to the client
+ * @see cacheControl
+ */
+ protected function sendCacheControlHeader()
+ {
+ session_cache_limiter('public');
+ $headers = Yii::$app->getResponse()->getHeaders();
+ $headers->set('Pragma');
+ if ($this->cacheControlHeader !== null) {
+ $headers->set('Cache-Control', $this->cacheControlHeader);
+ }
+ }
+
+ /**
+ * Generates an Etag from the given seed string.
+ * @param string $seed Seed for the ETag
+ * @return string the generated Etag
+ */
+ protected function generateEtag($seed)
+ {
+ return '"' . base64_encode(sha1($seed, true)) . '"';
+ }
}
diff --git a/framework/web/HttpException.php b/framework/web/HttpException.php
index 630419d44e0..a1dffd68b90 100644
--- a/framework/web/HttpException.php
+++ b/framework/web/HttpException.php
@@ -29,33 +29,33 @@
*/
class HttpException extends UserException
{
- /**
- * @var integer HTTP status code, such as 403, 404, 500, etc.
- */
- public $statusCode;
+ /**
+ * @var integer HTTP status code, such as 403, 404, 500, etc.
+ */
+ public $statusCode;
- /**
- * Constructor.
- * @param integer $status HTTP status code, such as 404, 500, etc.
- * @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, $message = null, $code = 0, \Exception $previous = null)
- {
- $this->statusCode = $status;
- parent::__construct($message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param integer $status HTTP status code, such as 404, 500, etc.
+ * @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, $message = null, $code = 0, \Exception $previous = null)
+ {
+ $this->statusCode = $status;
+ parent::__construct($message, $code, $previous);
+ }
- /**
- * @return string the user-friendly name of this exception
- */
- public function getName()
- {
- if (isset(Response::$httpStatuses[$this->statusCode])) {
- return Response::$httpStatuses[$this->statusCode];
- } else {
- return 'Error';
- }
- }
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ if (isset(Response::$httpStatuses[$this->statusCode])) {
+ return Response::$httpStatuses[$this->statusCode];
+ } else {
+ return 'Error';
+ }
+ }
}
diff --git a/framework/web/IdentityInterface.php b/framework/web/IdentityInterface.php
index 2aac17f1fc7..99e18fe6e18 100644
--- a/framework/web/IdentityInterface.php
+++ b/framework/web/IdentityInterface.php
@@ -43,47 +43,47 @@
*/
interface IdentityInterface
{
- /**
- * Finds an identity by the given ID.
- * @param string|integer $id the ID to be looked for
- * @return IdentityInterface the identity object that matches the given ID.
- * Null should be returned if such an identity cannot be found
- * or the identity is not in an active state (disabled, deleted, etc.)
- */
- public static function findIdentity($id);
- /**
- * Finds an identity by the given secrete token.
- * @param string $token the secrete token
- * @return IdentityInterface the identity object that matches the given token.
- * Null should be returned if such an identity cannot be found
- * or the identity is not in an active state (disabled, deleted, etc.)
- */
- public static function findIdentityByAccessToken($token);
- /**
- * Returns an ID that can uniquely identify a user identity.
- * @return string|integer an ID that uniquely identifies a user identity.
- */
- public function getId();
- /**
- * Returns a key that can be used to check the validity of a given identity ID.
- *
- * The key should be unique for each individual user, and should be persistent
- * so that it can be used to check the validity of the user identity.
- *
- * The space of such keys should be big enough to defeat potential identity attacks.
- *
- * This is required if [[User::enableAutoLogin]] is enabled.
- * @return string a key that is used to check the validity of a given identity ID.
- * @see validateAuthKey()
- */
- public function getAuthKey();
- /**
- * Validates the given auth key.
- *
- * This is required if [[User::enableAutoLogin]] is enabled.
- * @param string $authKey the given auth key
- * @return boolean whether the given auth key is valid.
- * @see getAuthKey()
- */
- public function validateAuthKey($authKey);
+ /**
+ * Finds an identity by the given ID.
+ * @param string|integer $id the ID to be looked for
+ * @return IdentityInterface the identity object that matches the given ID.
+ * Null should be returned if such an identity cannot be found
+ * or the identity is not in an active state (disabled, deleted, etc.)
+ */
+ public static function findIdentity($id);
+ /**
+ * Finds an identity by the given secrete token.
+ * @param string $token the secrete token
+ * @return IdentityInterface the identity object that matches the given token.
+ * Null should be returned if such an identity cannot be found
+ * or the identity is not in an active state (disabled, deleted, etc.)
+ */
+ public static function findIdentityByAccessToken($token);
+ /**
+ * Returns an ID that can uniquely identify a user identity.
+ * @return string|integer an ID that uniquely identifies a user identity.
+ */
+ public function getId();
+ /**
+ * Returns a key that can be used to check the validity of a given identity ID.
+ *
+ * The key should be unique for each individual user, and should be persistent
+ * so that it can be used to check the validity of the user identity.
+ *
+ * The space of such keys should be big enough to defeat potential identity attacks.
+ *
+ * This is required if [[User::enableAutoLogin]] is enabled.
+ * @return string a key that is used to check the validity of a given identity ID.
+ * @see validateAuthKey()
+ */
+ public function getAuthKey();
+ /**
+ * Validates the given auth key.
+ *
+ * This is required if [[User::enableAutoLogin]] is enabled.
+ * @param string $authKey the given auth key
+ * @return boolean whether the given auth key is valid.
+ * @see getAuthKey()
+ */
+ public function validateAuthKey($authKey);
}
diff --git a/framework/web/JqueryAsset.php b/framework/web/JqueryAsset.php
index 90d2df6b69b..96cbf032b44 100644
--- a/framework/web/JqueryAsset.php
+++ b/framework/web/JqueryAsset.php
@@ -15,8 +15,8 @@
*/
class JqueryAsset extends AssetBundle
{
- public $sourcePath = '@vendor/yiisoft/jquery';
- public $js = [
- 'jquery.js',
- ];
+ public $sourcePath = '@vendor/yiisoft/jquery';
+ public $js = [
+ 'jquery.js',
+ ];
}
diff --git a/framework/web/JsExpression.php b/framework/web/JsExpression.php
index eedd277a1a9..8c5db39c362 100644
--- a/framework/web/JsExpression.php
+++ b/framework/web/JsExpression.php
@@ -20,28 +20,28 @@
*/
class JsExpression extends Object
{
- /**
- * @var string the JavaScript expression represented by this object
- */
- public $expression;
+ /**
+ * @var string the JavaScript expression represented by this object
+ */
+ public $expression;
- /**
- * Constructor.
- * @param string $expression the JavaScript expression represented by this object
- * @param array $config additional configurations for this object
- */
- public function __construct($expression, $config = [])
- {
- $this->expression = $expression;
- parent::__construct($config);
- }
+ /**
+ * Constructor.
+ * @param string $expression the JavaScript expression represented by this object
+ * @param array $config additional configurations for this object
+ */
+ public function __construct($expression, $config = [])
+ {
+ $this->expression = $expression;
+ parent::__construct($config);
+ }
- /**
- * The PHP magic function converting an object into a string.
- * @return string the JavaScript expression.
- */
- public function __toString()
- {
- return $this->expression;
- }
+ /**
+ * The PHP magic function converting an object into a string.
+ * @return string the JavaScript expression.
+ */
+ public function __toString()
+ {
+ return $this->expression;
+ }
}
diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php
index 61dec1b1eae..a7e00446c3d 100644
--- a/framework/web/JsonParser.php
+++ b/framework/web/JsonParser.php
@@ -18,32 +18,32 @@
*/
class JsonParser implements RequestParserInterface
{
- /**
- * @var boolean whether to return objects in terms of associative arrays.
- */
- public $asArray = true;
- /**
- * @var boolean whether to throw a [[BadRequestHttpException]] if the body is invalid json
- */
- public $throwException = true;
+ /**
+ * @var boolean whether to return objects in terms of associative arrays.
+ */
+ public $asArray = true;
+ /**
+ * @var boolean whether to throw a [[BadRequestHttpException]] if the body is invalid json
+ */
+ public $throwException = true;
+ /**
+ * Parses a HTTP request body.
+ * @param string $rawBody the raw HTTP request body.
+ * @param string $contentType the content type specified for the request body.
+ * @return array parameters parsed from the request body
+ * @throws BadRequestHttpException if the body contains invalid json and [[throwException]] is `true`.
+ */
+ public function parse($rawBody, $contentType)
+ {
+ try {
+ return Json::decode($rawBody, $this->asArray);
+ } catch (InvalidParamException $e) {
+ if ($this->throwException) {
+ throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage(), 0, $e);
+ }
- /**
- * Parses a HTTP request body.
- * @param string $rawBody the raw HTTP request body.
- * @param string $contentType the content type specified for the request body.
- * @return array parameters parsed from the request body
- * @throws BadRequestHttpException if the body contains invalid json and [[throwException]] is `true`.
- */
- public function parse($rawBody, $contentType)
- {
- try {
- return Json::decode($rawBody, $this->asArray);
- } catch (InvalidParamException $e) {
- if ($this->throwException) {
- throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage(), 0, $e);
- }
- return null;
- }
- }
+ return null;
+ }
+ }
}
diff --git a/framework/web/Link.php b/framework/web/Link.php
index a2a4057e38d..e872deae13b 100644
--- a/framework/web/Link.php
+++ b/framework/web/Link.php
@@ -17,59 +17,59 @@
*/
class Link extends Object
{
- /**
- * The self link.
- */
- const REL_SELF = 'self';
+ /**
+ * The self link.
+ */
+ const REL_SELF = 'self';
- /**
- * @var string a URI [RFC3986](https://tools.ietf.org/html/rfc3986) or
- * URI template [RFC6570](https://tools.ietf.org/html/rfc6570). This property is required.
- */
- public $href;
- /**
- * @var string a secondary key for selecting Link Objects which share the same relation type
- */
- public $name;
- /**
- * @var string a hint to indicate the media type expected when dereferencing the target resource
- */
- public $type;
- /**
- * @var boolean a value indicating whether [[href]] refers to a URI or URI template.
- */
- public $templated = false;
- /**
- * @var string a URI that hints about the profile of the target resource.
- */
- public $profile;
- /**
- * @var string a label describing the link
- */
- public $title;
- /**
- * @var string the language of the target resource
- */
- public $hreflang;
+ /**
+ * @var string a URI [RFC3986](https://tools.ietf.org/html/rfc3986) or
+ * URI template [RFC6570](https://tools.ietf.org/html/rfc6570). This property is required.
+ */
+ public $href;
+ /**
+ * @var string a secondary key for selecting Link Objects which share the same relation type
+ */
+ public $name;
+ /**
+ * @var string a hint to indicate the media type expected when dereferencing the target resource
+ */
+ public $type;
+ /**
+ * @var boolean a value indicating whether [[href]] refers to a URI or URI template.
+ */
+ public $templated = false;
+ /**
+ * @var string a URI that hints about the profile of the target resource.
+ */
+ public $profile;
+ /**
+ * @var string a label describing the link
+ */
+ public $title;
+ /**
+ * @var string the language of the target resource
+ */
+ public $hreflang;
+ /**
+ * Serializes a list of links into proper array format.
+ * @param array $links the links to be serialized
+ * @return array the proper array representation of the links.
+ */
+ public static function serialize(array $links)
+ {
+ foreach ($links as $rel => $link) {
+ if (is_array($link)) {
+ foreach ($link as $i => $l) {
+ $link[$i] = $l instanceof self ? array_filter((array) $l) : ['href' => $l];
+ }
+ $links[$rel] = $link;
+ } elseif (!$link instanceof self) {
+ $links[$rel] = ['href' => $link];
+ }
+ }
- /**
- * Serializes a list of links into proper array format.
- * @param array $links the links to be serialized
- * @return array the proper array representation of the links.
- */
- public static function serialize(array $links)
- {
- foreach ($links as $rel => $link) {
- if (is_array($link)) {
- foreach ($link as $i => $l) {
- $link[$i] = $l instanceof self ? array_filter((array)$l) : ['href' => $l];
- }
- $links[$rel] = $link;
- } elseif (!$link instanceof self) {
- $links[$rel] = ['href' => $link];
- }
- }
- return $links;
- }
+ return $links;
+ }
}
diff --git a/framework/web/Linkable.php b/framework/web/Linkable.php
index 8d1558b05ff..faa82cfacb2 100644
--- a/framework/web/Linkable.php
+++ b/framework/web/Linkable.php
@@ -15,28 +15,28 @@
*/
interface Linkable
{
- /**
- * Returns a list of links.
- *
- * Each link is either a URI or a [[Link]] object. The return value of this method should
- * be an array whose keys are the relation names and values the corresponding links.
- *
- * If a relation name corresponds to multiple links, use an array to represent them.
- *
- * For example,
- *
- * ```php
- * [
- * 'self' => 'http://example.com/users/1',
- * 'friends' => [
- * 'http://example.com/users/2',
- * 'http://example.com/users/3',
- * ],
- * 'manager' => $managerLink, // $managerLink is a Link object
- * ]
- * ```
- *
- * @return array the links
- */
- public function getLinks();
+ /**
+ * Returns a list of links.
+ *
+ * Each link is either a URI or a [[Link]] object. The return value of this method should
+ * be an array whose keys are the relation names and values the corresponding links.
+ *
+ * If a relation name corresponds to multiple links, use an array to represent them.
+ *
+ * For example,
+ *
+ * ```php
+ * [
+ * 'self' => 'http://example.com/users/1',
+ * 'friends' => [
+ * 'http://example.com/users/2',
+ * 'http://example.com/users/3',
+ * ],
+ * 'manager' => $managerLink, // $managerLink is a Link object
+ * ]
+ * ```
+ *
+ * @return array the links
+ */
+ public function getLinks();
}
diff --git a/framework/web/MethodNotAllowedHttpException.php b/framework/web/MethodNotAllowedHttpException.php
index d894f57b87d..50d9120cb97 100644
--- a/framework/web/MethodNotAllowedHttpException.php
+++ b/framework/web/MethodNotAllowedHttpException.php
@@ -15,14 +15,14 @@
*/
class MethodNotAllowedHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(405, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(405, $message, $code, $previous);
+ }
}
diff --git a/framework/web/NotAcceptableHttpException.php b/framework/web/NotAcceptableHttpException.php
index 5a749c91e85..8c76206b333 100644
--- a/framework/web/NotAcceptableHttpException.php
+++ b/framework/web/NotAcceptableHttpException.php
@@ -20,14 +20,14 @@
*/
class NotAcceptableHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(406, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(406, $message, $code, $previous);
+ }
}
diff --git a/framework/web/NotFoundHttpException.php b/framework/web/NotFoundHttpException.php
index 71f246d8460..ffdc546fcd9 100644
--- a/framework/web/NotFoundHttpException.php
+++ b/framework/web/NotFoundHttpException.php
@@ -15,14 +15,14 @@
*/
class NotFoundHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(404, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(404, $message, $code, $previous);
+ }
}
diff --git a/framework/web/PageCache.php b/framework/web/PageCache.php
index 08857d530ba..15ee95fdc82 100644
--- a/framework/web/PageCache.php
+++ b/framework/web/PageCache.php
@@ -49,99 +49,100 @@
*/
class PageCache extends ActionFilter
{
- /**
- * @var boolean whether the content being cached should be differentiated according to the route.
- * A route consists of the requested controller ID and action ID. Defaults to true.
- */
- public $varyByRoute = true;
- /**
- * @var string the application component ID of the [[\yii\caching\Cache|cache]] object.
- */
- public $cache = 'cache';
- /**
- * @var integer number of seconds that the data can remain valid in cache.
- * Use 0 to indicate that the cached data will never expire.
- */
- public $duration = 60;
- /**
- * @var array|Dependency the dependency that the cached content depends on.
- * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
- * For example,
- *
- * ~~~
- * [
- * 'class' => 'yii\caching\DbDependency',
- * 'sql' => 'SELECT MAX(lastModified) FROM Post',
- * ]
- * ~~~
- *
- * would make the output cache depends on the last modified time of all posts.
- * If any post has its modification time changed, the cached content would be invalidated.
- */
- public $dependency;
- /**
- * @var array list of factors that would cause the variation of the content being cached.
- * Each factor is a string representing a variation (e.g. the language, a GET parameter).
- * The following variation setting will cause the content to be cached in different versions
- * according to the current application language:
- *
- * ~~~
- * [
- * Yii::$app->language,
- * ]
- * ~~~
- */
- public $variations;
- /**
- * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
- * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
- */
- public $enabled = true;
- /**
- * @var \yii\base\View the view component to use for caching. If not set, the default application view component
- * [[Application::view]] will be used.
- */
- public $view;
+ /**
+ * @var boolean whether the content being cached should be differentiated according to the route.
+ * A route consists of the requested controller ID and action ID. Defaults to true.
+ */
+ public $varyByRoute = true;
+ /**
+ * @var string the application component ID of the [[\yii\caching\Cache|cache]] object.
+ */
+ public $cache = 'cache';
+ /**
+ * @var integer number of seconds that the data can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ */
+ public $duration = 60;
+ /**
+ * @var array|Dependency the dependency that the cached content depends on.
+ * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
+ * For example,
+ *
+ * ~~~
+ * [
+ * 'class' => 'yii\caching\DbDependency',
+ * 'sql' => 'SELECT MAX(lastModified) FROM Post',
+ * ]
+ * ~~~
+ *
+ * would make the output cache depends on the last modified time of all posts.
+ * If any post has its modification time changed, the cached content would be invalidated.
+ */
+ public $dependency;
+ /**
+ * @var array list of factors that would cause the variation of the content being cached.
+ * Each factor is a string representing a variation (e.g. the language, a GET parameter).
+ * The following variation setting will cause the content to be cached in different versions
+ * according to the current application language:
+ *
+ * ~~~
+ * [
+ * Yii::$app->language,
+ * ]
+ * ~~~
+ */
+ public $variations;
+ /**
+ * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
+ * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
+ */
+ public $enabled = true;
+ /**
+ * @var \yii\base\View the view component to use for caching. If not set, the default application view component
+ * [[Application::view]] will be used.
+ */
+ public $view;
+ public function init()
+ {
+ parent::init();
+ if ($this->view === null) {
+ $this->view = Yii::$app->getView();
+ }
+ }
- public function init()
- {
- parent::init();
- if ($this->view === null) {
- $this->view = Yii::$app->getView();
- }
- }
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $properties = [];
+ foreach (['cache', 'duration', 'dependency', 'variations', 'enabled'] as $name) {
+ $properties[$name] = $this->$name;
+ }
+ $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
+ ob_start();
+ ob_implicit_flush(false);
+ if ($this->view->beginCache($id, $properties)) {
+ return true;
+ } else {
+ Yii::$app->getResponse()->content = ob_get_clean();
- /**
- * This method is invoked right before an action is to be executed (after all possible filters.)
- * You may override this method to do last-minute preparation for the action.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- $properties = [];
- foreach (['cache', 'duration', 'dependency', 'variations', 'enabled'] as $name) {
- $properties[$name] = $this->$name;
- }
- $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
- ob_start();
- ob_implicit_flush(false);
- if ($this->view->beginCache($id, $properties)) {
- return true;
- } else {
- Yii::$app->getResponse()->content = ob_get_clean();
- return false;
- }
- }
+ return false;
+ }
+ }
- /**
- * @inheritdoc
- */
- public function afterAction($action, $result)
- {
- echo $result;
- $this->view->endCache();
- return ob_get_clean();
- }
+ /**
+ * @inheritdoc
+ */
+ public function afterAction($action, $result)
+ {
+ echo $result;
+ $this->view->endCache();
+
+ return ob_get_clean();
+ }
}
diff --git a/framework/web/Request.php b/framework/web/Request.php
index e772ba23468..0d00059f486 100644
--- a/framework/web/Request.php
+++ b/framework/web/Request.php
@@ -83,1229 +83,1255 @@
*/
class Request extends \yii\base\Request
{
- /**
- * The name of the HTTP header for sending CSRF token.
- */
- const CSRF_HEADER = 'X-CSRF-Token';
- /**
- * The length of the CSRF token mask.
- */
- const CSRF_MASK_LENGTH = 8;
-
-
- /**
- * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
- * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
- * from the same application. If not, a 400 HTTP exception will be raised.
- *
- * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
- * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
- * You may use [[\yii\web\Html::beginForm()]] to generate his hidden input.
- *
- * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
- * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
- *
- * @see Controller::enableCsrfValidation
- * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
- */
- public $enableCsrfValidation = true;
- /**
- * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
- * This property is used only when [[enableCsrfValidation]] is true.
- */
- public $csrfParam = '_csrf';
- /**
- * @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
- * @see Cookie
- */
- public $csrfCookie = ['httpOnly' => true];
- /**
- * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
- */
- public $enableCookieValidation = true;
- /**
- * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
- * request tunneled through POST. Default to '_method'.
- * @see getMethod()
- * @see getBodyParams()
- */
- public $methodParam = '_method';
- /**
- * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
- * The array keys are the request `Content-Types`, and the array values are the
- * corresponding configurations for [[Yii::createObject|creating the parser objects]].
- * A parser must implement the [[RequestParserInterface]].
- *
- * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
- *
- * ```
- * [
- * 'application/json' => 'yii\web\JsonParser',
- * ]
- * ```
- *
- * To register a parser for parsing all request types you can use `'*'` as the array key.
- * This one will be used as a fallback in case no other types match.
- *
- * @see getBodyParams()
- */
- public $parsers = [];
-
- /**
- * @var CookieCollection Collection of request cookies.
- */
- private $_cookies;
- /**
- * @var array the headers in this collection (indexed by the header names)
- */
- private $_headers;
-
-
- /**
- * Resolves the current request into a route and the associated parameters.
- * @return array the first element is the route, and the second is the associated parameters.
- * @throws HttpException if the request cannot be resolved.
- */
- public function resolve()
- {
- $result = Yii::$app->getUrlManager()->parseRequest($this);
- if ($result !== false) {
- list ($route, $params) = $result;
- $_GET = array_merge($_GET, $params);
- return [$route, $_GET];
- } else {
- throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
- }
- }
-
- /**
- * Returns the header collection.
- * The header collection contains incoming HTTP headers.
- * @return HeaderCollection the header collection
- */
- public function getHeaders()
- {
- if ($this->_headers === null) {
- $this->_headers = new HeaderCollection;
- if (function_exists('getallheaders')) {
- $headers = getallheaders();
- } elseif (function_exists('http_get_request_headers')) {
- $headers = http_get_request_headers();
- } else {
- foreach ($_SERVER as $name => $value) {
- if (strncmp($name, 'HTTP_', 5) === 0) {
- $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
- $this->_headers->add($name, $value);
- }
- }
- return $this->_headers;
- }
- foreach ($headers as $name => $value) {
- $this->_headers->add($name, $value);
- }
- }
-
- return $this->_headers;
- }
-
- /**
- * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
- * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
- * The value returned is turned into upper case.
- */
- public function getMethod()
- {
- if (isset($_POST[$this->methodParam])) {
- return strtoupper($_POST[$this->methodParam]);
- } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
- return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
- } else {
- return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
- }
- }
-
- /**
- * Returns whether this is a GET request.
- * @return boolean whether this is a GET request.
- */
- public function getIsGet()
- {
- return $this->getMethod() === 'GET';
- }
-
- /**
- * Returns whether this is an OPTIONS request.
- * @return boolean whether this is a OPTIONS request.
- */
- public function getIsOptions()
- {
- return $this->getMethod() === 'OPTIONS';
- }
-
- /**
- * Returns whether this is a HEAD request.
- * @return boolean whether this is a HEAD request.
- */
- public function getIsHead()
- {
- return $this->getMethod() === 'HEAD';
- }
-
- /**
- * Returns whether this is a POST request.
- * @return boolean whether this is a POST request.
- */
- public function getIsPost()
- {
- return $this->getMethod() === 'POST';
- }
-
- /**
- * Returns whether this is a DELETE request.
- * @return boolean whether this is a DELETE request.
- */
- public function getIsDelete()
- {
- return $this->getMethod() === 'DELETE';
- }
-
- /**
- * Returns whether this is a PUT request.
- * @return boolean whether this is a PUT request.
- */
- public function getIsPut()
- {
- return $this->getMethod() === 'PUT';
- }
-
- /**
- * Returns whether this is a PATCH request.
- * @return boolean whether this is a PATCH request.
- */
- public function getIsPatch()
- {
- return $this->getMethod() === 'PATCH';
- }
-
- /**
- * Returns whether this is an AJAX (XMLHttpRequest) request.
- * @return boolean whether this is an AJAX (XMLHttpRequest) request.
- */
- public function getIsAjax()
- {
- return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
- }
-
- /**
- * Returns whether this is a PJAX request
- * @return boolean whether this is a PJAX request
- */
- public function getIsPjax ()
- {
- return $this->getIsAjax() && !empty($_SERVER['HTTP_X_PJAX']);
- }
-
- /**
- * Returns whether this is an Adobe Flash or Flex request.
- * @return boolean whether this is an Adobe Flash or Adobe Flex request.
- */
- public function getIsFlash()
- {
- return isset($_SERVER['HTTP_USER_AGENT']) &&
- (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false);
- }
-
- private $_rawBody;
-
- /**
- * Returns the raw HTTP request body.
- * @return string the request body
- */
- public function getRawBody()
- {
- if ($this->_rawBody === null) {
- $this->_rawBody = file_get_contents('php://input');
- }
- return $this->_rawBody;
- }
-
- private $_bodyParams;
-
- /**
- * Returns the request parameters given in the request body.
- *
- * Request parameters are determined using the parsers configured in [[parsers]] property.
- * If no parsers are configured for the current [[contentType]] it uses the PHP function [[mb_parse_str()]]
- * to parse the [[rawBody|request body]].
- * @return array the request parameters given in the request body.
- * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
- * @see getMethod()
- * @see getBodyParam()
- * @see setBodyParams()
- */
- public function getBodyParams()
- {
- if ($this->_bodyParams === null) {
- $contentType = $this->getContentType();
- if (isset($_POST[$this->methodParam])) {
- $this->_bodyParams = $_POST;
- unset($this->_bodyParams[$this->methodParam]);
- } elseif (isset($this->parsers[$contentType])) {
- $parser = Yii::createObject($this->parsers[$contentType]);
- if (!($parser instanceof RequestParserInterface)) {
- throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
- }
- $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType);
- } elseif (isset($this->parsers['*'])) {
- $parser = Yii::createObject($this->parsers['*']);
- if (!($parser instanceof RequestParserInterface)) {
- throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
- }
- $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType);
- } elseif ($this->getMethod() === 'POST') {
- // PHP has already parsed the body so we have all params in $_POST
- $this->_bodyParams = $_POST;
- } else {
- $this->_bodyParams = [];
- mb_parse_str($this->getRawBody(), $this->_bodyParams);
- }
- }
- return $this->_bodyParams;
- }
-
- /**
- * Sets the request body parameters.
- * @param array $values the request body parameters (name-value pairs)
- * @see getBodyParam()
- * @see getBodyParams()
- */
- public function setBodyParams($values)
- {
- $this->_bodyParams = $values;
- }
-
- /**
- * Returns the named request body parameter value.
- * @param string $name the parameter name
- * @param mixed $defaultValue the default parameter value if the parameter does not exist.
- * @return mixed the parameter value
- * @see getBodyParams()
- * @see setBodyParams()
- */
- public function getBodyParam($name, $defaultValue = null)
- {
- $params = $this->getBodyParams();
- return isset($params[$name]) ? $params[$name] : $defaultValue;
- }
-
- /**
- * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
- *
- * @param string $name the parameter name
- * @param mixed $defaultValue the default parameter value if the parameter does not exist.
- * @return array|mixed
- */
- public function post($name = null, $defaultValue = null)
- {
- if ($name === null) {
- return $this->getBodyParams();
- } else {
- return $this->getBodyParam($name, $defaultValue);
- }
- }
-
- private $_queryParams;
-
- /**
- * Returns the request parameters given in the [[queryString]].
- *
- * This method will return the contents of `$_GET` if params where not explicitly set.
- * @return array the request GET parameter values.
- * @see setQueryParams()
- */
- public function getQueryParams()
- {
- if ($this->_queryParams === null) {
- return $_GET;
- }
- return $this->_queryParams;
- }
-
- /**
- * Sets the request [[queryString]] parameters.
- * @param array $values the request query parameters (name-value pairs)
- * @see getQueryParam()
- * @see getQueryParams()
- */
- public function setQueryParams($values)
- {
- $this->_queryParams = $values;
- }
-
- /**
- * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
- *
- * @param string $name the parameter name
- * @param mixed $defaultValue the default parameter value if the parameter does not exist.
- * @return array|mixed
- */
- public function get($name = null, $defaultValue = null)
- {
- if ($name === null) {
- return $this->getQueryParams();
- } else {
- return $this->getQueryParam($name, $defaultValue);
- }
- }
-
- /**
- * Returns the named GET parameter value.
- * If the GET parameter does not exist, the second parameter to this method will be returned.
- * @param string $name the GET parameter name. If not specified, whole $_GET is returned.
- * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
- * @return mixed the GET parameter value
- * @see getBodyParam()
- */
- public function getQueryParam($name, $defaultValue = null)
- {
- $params = $this->getQueryParams();
- return isset($params[$name]) ? $params[$name] : $defaultValue;
- }
-
- private $_hostInfo;
-
- /**
- * Returns the schema and host part of the current request URL.
- * The returned URL does not have an ending slash.
- * By default this is determined based on the user request information.
- * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
- * @return string schema and hostname part (with port number if needed) of the request URL (e.g. `http://www.yiiframework.com`)
- * @see setHostInfo()
- */
- public function getHostInfo()
- {
- if ($this->_hostInfo === null) {
- $secure = $this->getIsSecureConnection();
- $http = $secure ? 'https' : 'http';
- if (isset($_SERVER['HTTP_HOST'])) {
- $this->_hostInfo = $http . '://' . $_SERVER['HTTP_HOST'];
- } else {
- $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
- $port = $secure ? $this->getSecurePort() : $this->getPort();
- if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
- $this->_hostInfo .= ':' . $port;
- }
- }
- }
-
- return $this->_hostInfo;
- }
-
- /**
- * Sets the schema and host part of the application URL.
- * This setter is provided in case the schema and hostname cannot be determined
- * on certain Web servers.
- * @param string $value the schema and host part of the application URL. The trailing slashes will be removed.
- */
- public function setHostInfo($value)
- {
- $this->_hostInfo = rtrim($value, '/');
- }
-
- private $_baseUrl;
-
- /**
- * Returns the relative URL for the application.
- * This is similar to [[scriptUrl]] except that it does not include the script file name,
- * and the ending slashes are removed.
- * @return string the relative URL for the application
- * @see setScriptUrl()
- */
- public function getBaseUrl()
- {
- if ($this->_baseUrl === null) {
- $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
- }
- return $this->_baseUrl;
- }
-
- /**
- * Sets the relative URL for the application.
- * By default the URL is determined based on the entry script URL.
- * This setter is provided in case you want to change this behavior.
- * @param string $value the relative URL for the application
- */
- public function setBaseUrl($value)
- {
- $this->_baseUrl = $value;
- }
-
- private $_scriptUrl;
-
- /**
- * Returns the relative URL of the entry script.
- * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
- * @return string the relative URL of the entry script.
- * @throws InvalidConfigException if unable to determine the entry script URL
- */
- public function getScriptUrl()
- {
- if ($this->_scriptUrl === null) {
- $scriptFile = $this->getScriptFile();
- $scriptName = basename($scriptFile);
- if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
- $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
- } elseif (basename($_SERVER['PHP_SELF']) === $scriptName) {
- $this->_scriptUrl = $_SERVER['PHP_SELF'];
- } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
- $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
- } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
- $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
- } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
- $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
- } else {
- throw new InvalidConfigException('Unable to determine the entry script URL.');
- }
- }
- return $this->_scriptUrl;
- }
-
- /**
- * Sets the relative URL for the application entry script.
- * This setter is provided in case the entry script URL cannot be determined
- * on certain Web servers.
- * @param string $value the relative URL for the application entry script.
- */
- public function setScriptUrl($value)
- {
- $this->_scriptUrl = '/' . trim($value, '/');
- }
-
- private $_scriptFile;
-
- /**
- * Returns the entry script file path.
- * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
- * @return string the entry script file path
- */
- public function getScriptFile()
- {
- return isset($this->_scriptFile) ? $this->_scriptFile : $_SERVER['SCRIPT_FILENAME'];
- }
-
- /**
- * Sets the entry script file path.
- * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
- * If your server configuration does not return the correct value, you may configure
- * this property to make it right.
- * @param string $value the entry script file path.
- */
- public function setScriptFile($value)
- {
- $this->_scriptFile = $value;
- }
-
- private $_pathInfo;
-
- /**
- * Returns the path info of the currently requested URL.
- * A path info refers to the part that is after the entry script and before the question mark (query string).
- * The starting and ending slashes are both removed.
- * @return string part of the request URL that is after the entry script and before the question mark.
- * Note, the returned path info is already URL-decoded.
- * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
- */
- public function getPathInfo()
- {
- if ($this->_pathInfo === null) {
- $this->_pathInfo = $this->resolvePathInfo();
- }
- return $this->_pathInfo;
- }
-
- /**
- * Sets the path info of the current request.
- * This method is mainly provided for testing purpose.
- * @param string $value the path info of the current request
- */
- public function setPathInfo($value)
- {
- $this->_pathInfo = ltrim($value, '/');
- }
-
- /**
- * Resolves the path info part of the currently requested URL.
- * A path info refers to the part that is after the entry script and before the question mark (query string).
- * The starting slashes are both removed (ending slashes will be kept).
- * @return string part of the request URL that is after the entry script and before the question mark.
- * Note, the returned path info is decoded.
- * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
- */
- protected function resolvePathInfo()
- {
- $pathInfo = $this->getUrl();
-
- if (($pos = strpos($pathInfo, '?')) !== false) {
- $pathInfo = substr($pathInfo, 0, $pos);
- }
-
- $pathInfo = urldecode($pathInfo);
-
- // try to encode in UTF8 if not so
- // http://w3.org/International/questions/qa-forms-utf-8.html
- if (!preg_match('%^(?:
- [\x09\x0A\x0D\x20-\x7E] # ASCII
- | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
- | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
- | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
- | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
- | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
- | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
- | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
- )*$%xs', $pathInfo)) {
- $pathInfo = utf8_encode($pathInfo);
- }
-
- $scriptUrl = $this->getScriptUrl();
- $baseUrl = $this->getBaseUrl();
- if (strpos($pathInfo, $scriptUrl) === 0) {
- $pathInfo = substr($pathInfo, strlen($scriptUrl));
- } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
- $pathInfo = substr($pathInfo, strlen($baseUrl));
- } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
- $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
- } else {
- throw new InvalidConfigException('Unable to determine the path info of the current request.');
- }
-
- if ($pathInfo[0] === '/') {
- $pathInfo = substr($pathInfo, 1);
- }
-
- return (string)$pathInfo;
- }
-
- /**
- * Returns the currently requested absolute URL.
- * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
- * @return string the currently requested absolute URL.
- */
- public function getAbsoluteUrl()
- {
- return $this->getHostInfo() . $this->getUrl();
- }
-
- private $_url;
-
- /**
- * Returns the currently requested relative URL.
- * This refers to the portion of the URL that is after the [[hostInfo]] part.
- * It includes the [[queryString]] part if any.
- * @return string the currently requested relative URL. Note that the URI returned is URL-encoded.
- * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
- */
- public function getUrl()
- {
- if ($this->_url === null) {
- $this->_url = $this->resolveRequestUri();
- }
- return $this->_url;
- }
-
- /**
- * Sets the currently requested relative URL.
- * The URI must refer to the portion that is after [[hostInfo]].
- * Note that the URI should be URL-encoded.
- * @param string $value the request URI to be set
- */
- public function setUrl($value)
- {
- $this->_url = $value;
- }
-
- /**
- * Resolves the request URI portion for the currently requested URL.
- * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
- * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
- * @return string|boolean the request URI portion for the currently requested URL.
- * Note that the URI returned is URL-encoded.
- * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
- */
- protected function resolveRequestUri()
- {
- if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS
- $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
- } elseif (isset($_SERVER['REQUEST_URI'])) {
- $requestUri = $_SERVER['REQUEST_URI'];
- if ($requestUri !== '' && $requestUri[0] !== '/') {
- $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
- }
- } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
- $requestUri = $_SERVER['ORIG_PATH_INFO'];
- if (!empty($_SERVER['QUERY_STRING'])) {
- $requestUri .= '?' . $_SERVER['QUERY_STRING'];
- }
- } else {
- throw new InvalidConfigException('Unable to determine the request URI.');
- }
- return $requestUri;
- }
-
- /**
- * Returns part of the request URL that is after the question mark.
- * @return string part of the request URL that is after the question mark
- */
- public function getQueryString()
- {
- return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
- }
-
- /**
- * Return if the request is sent via secure channel (https).
- * @return boolean if the request is sent via secure channel (https)
- */
- public function getIsSecureConnection()
- {
- return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
- || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
- }
-
- /**
- * Returns the server name.
- * @return string server name
- */
- public function getServerName()
- {
- return $_SERVER['SERVER_NAME'];
- }
-
- /**
- * Returns the server port number.
- * @return integer server port number
- */
- public function getServerPort()
- {
- return (int)$_SERVER['SERVER_PORT'];
- }
-
- /**
- * Returns the URL referrer, null if not present
- * @return string URL referrer, null if not present
- */
- public function getReferrer()
- {
- return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
- }
-
- /**
- * Returns the user agent, null if not present.
- * @return string user agent, null if not present
- */
- public function getUserAgent()
- {
- return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
- }
-
- /**
- * Returns the user IP address.
- * @return string user IP address
- */
- public function getUserIP()
- {
- return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
- }
-
- /**
- * Returns the user host name, null if it cannot be determined.
- * @return string user host name, null if cannot be determined
- */
- public function getUserHost()
- {
- return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
- }
-
- /**
- * @return string the username sent via HTTP authentication, null if the username is not given
- */
- public function getAuthUser()
- {
- return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
- }
-
- /**
- * @return string the password sent via HTTP authentication, null if the password is not given
- */
- public function getAuthPassword()
- {
- return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
- }
-
- private $_port;
-
- /**
- * Returns the port to use for insecure requests.
- * Defaults to 80, or the port specified by the server if the current
- * request is insecure.
- * @return integer port number for insecure requests.
- * @see setPort()
- */
- public function getPort()
- {
- if ($this->_port === null) {
- $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 80;
- }
- return $this->_port;
- }
-
- /**
- * Sets the port to use for insecure requests.
- * This setter is provided in case a custom port is necessary for certain
- * server configurations.
- * @param integer $value port number.
- */
- public function setPort($value)
- {
- if ($value != $this->_port) {
- $this->_port = (int)$value;
- $this->_hostInfo = null;
- }
- }
-
- private $_securePort;
-
- /**
- * Returns the port to use for secure requests.
- * Defaults to 443, or the port specified by the server if the current
- * request is secure.
- * @return integer port number for secure requests.
- * @see setSecurePort()
- */
- public function getSecurePort()
- {
- if ($this->_securePort === null) {
- $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 443;
- }
- return $this->_securePort;
- }
-
- /**
- * Sets the port to use for secure requests.
- * This setter is provided in case a custom port is necessary for certain
- * server configurations.
- * @param integer $value port number.
- */
- public function setSecurePort($value)
- {
- if ($value != $this->_securePort) {
- $this->_securePort = (int)$value;
- $this->_hostInfo = null;
- }
- }
-
- private $_contentTypes;
-
- /**
- * Returns the content types acceptable by the end user.
- * This is determined by the `Accept` HTTP header. For example,
- *
- * ```php
- * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
- * $types = $request->getAcceptableContentTypes();
- * print_r($types);
- * // displays:
- * // [
- * // 'application/json' => ['q' => 1, 'version' => '1.0'],
- * // 'application/xml' => ['q' => 1, 'version' => '2.0'],
- * // 'text/plain' => ['q' => 0.5],
- * // ]
- * ```
- *
- * @return array the content types ordered by the quality score. Types with the highest scores
- * will be returned first. The array keys are the content types, while the array values
- * are the corresponding quality score and other parameters as given in the header.
- */
- public function getAcceptableContentTypes()
- {
- if ($this->_contentTypes === null) {
- if (isset($_SERVER['HTTP_ACCEPT'])) {
- $this->_contentTypes = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT']);
- } else {
- $this->_contentTypes = [];
- }
- }
- return $this->_contentTypes;
- }
-
- /**
- * Sets the acceptable content types.
- * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
- * @param array $value the content types that are acceptable by the end user. They should
- * be ordered by the preference level.
- * @see getAcceptableContentTypes()
- * @see parseAcceptHeader()
- */
- public function setAcceptableContentTypes($value)
- {
- $this->_contentTypes = $value;
- }
-
- /**
- * Returns request content-type
- * The Content-Type header field indicates the MIME type of the data
- * contained in [[getRawBody()]] or, in the case of the HEAD method, the
- * media type that would have been sent had the request been a GET.
- * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
- * @return string request content-type. Null is returned if this information is not available.
- * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
- * HTTP 1.1 header field definitions
- */
- public function getContentType()
- {
- if (isset($_SERVER["CONTENT_TYPE"])) {
- return $_SERVER["CONTENT_TYPE"];
- } elseif (isset($_SERVER["HTTP_CONTENT_TYPE"])) { //fix bug https://bugs.php.net/bug.php?id=66606
- return $_SERVER["HTTP_CONTENT_TYPE"];
- }
- return null;
- }
-
- private $_languages;
-
- /**
- * Returns the languages acceptable by the end user.
- * This is determined by the `Accept-Language` HTTP header.
- * @return array the languages ordered by the preference level. The first element
- * represents the most preferred language.
- */
- public function getAcceptableLanguages()
- {
- if ($this->_languages === null) {
- if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
- $this->_languages = array_keys($this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']));
- } else {
- $this->_languages = [];
- }
- }
- return $this->_languages;
- }
-
- /**
- * @param array $value the languages that are acceptable by the end user. They should
- * be ordered by the preference level.
- */
- public function setAcceptableLanguages($value)
- {
- $this->_languages = $value;
- }
-
- /**
- * Parses the given `Accept` (or `Accept-Language`) header.
- *
- * This method will return the acceptable values with their quality scores and the corresponding parameters
- * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
- * while the array values consisting of the corresponding quality scores and parameters. The acceptable
- * values with the highest quality scores will be returned first. For example,
- *
- * ```php
- * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
- * $accepts = $request->parseAcceptHeader($header);
- * print_r($accepts);
- * // displays:
- * // [
- * // 'application/json' => ['q' => 1, 'version' => '1.0'],
- * // 'application/xml' => ['q' => 1, 'version' => '2.0'],
- * // 'text/plain' => ['q' => 0.5],
- * // ]
- * ```
- *
- * @param string $header the header to be parsed
- * @return array the acceptable values ordered by their quality score. The values with the highest scores
- * will be returned first.
- */
- public function parseAcceptHeader($header)
- {
- $accepts = [];
- foreach (explode(',', $header) as $i => $part) {
- $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
- if (empty($params)) {
- continue;
- }
- $values = [
- 'q' => [$i, array_shift($params), 1],
- ];
- foreach ($params as $param) {
- if (strpos($param, '=') !== false) {
- list ($key, $value) = explode('=', $param, 2);
- if ($key === 'q') {
- $values['q'][2] = (double)$value;
- } else {
- $values[$key] = $value;
- }
- } else {
- $values[] = $param;
- }
- }
- $accepts[] = $values;
- }
-
- usort($accepts, function ($a, $b) {
- $a = $a['q']; // index, name, q
- $b = $b['q'];
- if ($a[2] > $b[2]) {
- return -1;
- } elseif ($a[2] < $b[2]) {
- return 1;
- } elseif ($a[1] === $b[1]) {
- return $a[0] > $b[0] ? 1 : -1;
- } elseif ($a[1] === '*/*') {
- return 1;
- } elseif ($b[1] === '*/*') {
- return -1;
- } else {
- $wa = $a[1][strlen($a[1]) - 1] === '*';
- $wb = $b[1][strlen($b[1]) - 1] === '*';
- if ($wa xor $wb) {
- return $wa ? 1 : -1;
- } else {
- return $a[0] > $b[0] ? 1 : -1;
- }
- }
- });
-
- $result = [];
- foreach ($accepts as $accept) {
- $name = $accept['q'][1];
- $accept['q'] = $accept['q'][2];
- $result[$name] = $accept;
- }
-
- return $result;
- }
-
- /**
- * Returns the user-preferred language that should be used by this application.
- * The language resolution is based on the user preferred languages and the languages
- * supported by the application. The method will try to find the best match.
- * @param array $languages a list of the languages supported by the application. If this is empty, the current
- * application language will be returned without further processing.
- * @return string the language that the application should use.
- */
- public function getPreferredLanguage(array $languages = [])
- {
- if (empty($languages)) {
- return Yii::$app->language;
- }
- foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
- $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
- foreach ($languages as $language) {
- $language = str_replace('_', '-', strtolower($language));
- // en-us==en-us, en==en-us, en-us==en
- if ($language === $acceptableLanguage || strpos($acceptableLanguage, $language . '-') === 0 || strpos($language, $acceptableLanguage . '-') === 0) {
- return $language;
- }
- }
- }
- return reset($languages);
- }
-
- /**
- * Returns the cookie collection.
- * Through the returned cookie collection, you may access a cookie using the following syntax:
- *
- * ~~~
- * $cookie = $request->cookies['name']
- * if ($cookie !== null) {
- * $value = $cookie->value;
- * }
- *
- * // alternatively
- * $value = $request->cookies->getValue('name');
- * ~~~
- *
- * @return CookieCollection the cookie collection.
- */
- public function getCookies()
- {
- if ($this->_cookies === null) {
- $this->_cookies = new CookieCollection($this->loadCookies(), [
- 'readOnly' => true,
- ]);
- }
- return $this->_cookies;
- }
-
- /**
- * Converts `$_COOKIE` into an array of [[Cookie]].
- * @return array the cookies obtained from request
- */
- protected function loadCookies()
- {
- $cookies = [];
- if ($this->enableCookieValidation) {
- $key = $this->getCookieValidationKey();
- foreach ($_COOKIE as $name => $value) {
- if (is_string($value) && ($value = Security::validateData($value, $key)) !== false) {
- $cookies[$name] = new Cookie([
- 'name' => $name,
- 'value' => @unserialize($value),
- ]);
- }
- }
- } else {
- foreach ($_COOKIE as $name => $value) {
- $cookies[$name] = new Cookie([
- 'name' => $name,
- 'value' => $value,
- ]);
- }
- }
- return $cookies;
- }
-
- private $_cookieValidationKey;
-
- /**
- * @return string the secret key used for cookie validation. If it was not set previously,
- * a random key will be generated and used.
- */
- public function getCookieValidationKey()
- {
- if ($this->_cookieValidationKey === null) {
- $this->_cookieValidationKey = Security::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
- }
- return $this->_cookieValidationKey;
- }
-
- /**
- * Sets the secret key used for cookie validation.
- * @param string $value the secret key used for cookie validation.
- */
- public function setCookieValidationKey($value)
- {
- $this->_cookieValidationKey = $value;
- }
-
- /**
- * @var Cookie
- */
- private $_csrfCookie;
-
- /**
- * Returns the unmasked random token used to perform CSRF validation.
- * This token is typically sent via a cookie. If such a cookie does not exist, a new token will be generated.
- * @return string the random token for CSRF validation.
- * @see enableCsrfValidation
- */
- public function getRawCsrfToken()
- {
- if ($this->_csrfCookie === null) {
- $this->_csrfCookie = $this->getCookies()->get($this->csrfParam);
- if ($this->_csrfCookie === null) {
- $this->_csrfCookie = $this->createCsrfCookie();
- Yii::$app->getResponse()->getCookies()->add($this->_csrfCookie);
- }
- }
-
- return $this->_csrfCookie->value;
- }
-
- private $_csrfToken;
-
- /**
- * Returns the token used to perform CSRF validation.
- *
- * This token is a masked version of [[rawCsrfToken]] to prevent [BREACH attacks](http://breachattack.com/).
- * This token may be passed along via a hidden field of an HTML form or an HTTP header value
- * to support CSRF validation.
- *
- * @return string the token used to perform CSRF validation.
- */
- public function getCsrfToken()
- {
- if ($this->_csrfToken === null) {
- // the mask doesn't need to be very random
- $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.';
- $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, self::CSRF_MASK_LENGTH);
-
- $token = $this->getRawCsrfToken();
- // The + sign may be decoded as blank space later, which will fail the validation
- $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
- }
- return $this->_csrfToken;
- }
-
- /**
- * Returns the XOR result of two strings.
- * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one.
- * @param string $token1
- * @param string $token2
- * @return string the XOR result
- */
- private function xorTokens($token1, $token2)
- {
- $n1 = StringHelper::byteLength($token1);
- $n2 = StringHelper::byteLength($token2);
- if ($n1 > $n2) {
- $token2 = str_pad($token2, $n1, $token2);
- } elseif ($n1 < $n2) {
- $token1 = str_pad($token1, $n2, $token1);
- }
- return $token1 ^ $token2;
- }
-
- /**
- * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
- */
- public function getCsrfTokenFromHeader()
- {
- $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER));
- return isset($_SERVER[$key]) ? $_SERVER[$key] : null;
- }
-
- /**
- * Creates a cookie with a randomly generated CSRF token.
- * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
- * @return Cookie the generated cookie
- * @see enableCsrfValidation
- */
- protected function createCsrfCookie()
- {
- $options = $this->csrfCookie;
- $options['name'] = $this->csrfParam;
- $options['value'] = Security::generateRandomKey();
- return new Cookie($options);
- }
-
- /**
- * Performs the CSRF validation.
- * The method will compare the CSRF token obtained from a cookie and from a POST field.
- * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised.
- * This method is called in [[Controller::beforeAction()]].
- * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
- */
- public function validateCsrfToken()
- {
- $method = $this->getMethod();
- // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
- if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
- return true;
- }
- $trueToken = $this->getCookies()->getValue($this->csrfParam);
- $token = $this->getBodyParam($this->csrfParam);
- return $this->validateCsrfTokenInternal($token, $trueToken)
- || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
- }
-
- private function validateCsrfTokenInternal($token, $trueToken)
- {
- $token = base64_decode(str_replace('.', '+', $token));
- $n = StringHelper::byteLength($token);
- if ($n <= self::CSRF_MASK_LENGTH) {
- return false;
- }
- $mask = StringHelper::byteSubstr($token, 0, self::CSRF_MASK_LENGTH);
- $token = StringHelper::byteSubstr($token, self::CSRF_MASK_LENGTH, $n - self::CSRF_MASK_LENGTH);
- $token = $this->xorTokens($mask, $token);
- return $token === $trueToken;
- }
+ /**
+ * The name of the HTTP header for sending CSRF token.
+ */
+ const CSRF_HEADER = 'X-CSRF-Token';
+ /**
+ * The length of the CSRF token mask.
+ */
+ const CSRF_MASK_LENGTH = 8;
+
+ /**
+ * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
+ * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
+ * from the same application. If not, a 400 HTTP exception will be raised.
+ *
+ * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
+ * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
+ * You may use [[\yii\web\Html::beginForm()]] to generate his hidden input.
+ *
+ * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
+ * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
+ *
+ * @see Controller::enableCsrfValidation
+ * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
+ */
+ public $enableCsrfValidation = true;
+ /**
+ * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
+ * This property is used only when [[enableCsrfValidation]] is true.
+ */
+ public $csrfParam = '_csrf';
+ /**
+ * @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
+ * @see Cookie
+ */
+ public $csrfCookie = ['httpOnly' => true];
+ /**
+ * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
+ */
+ public $enableCookieValidation = true;
+ /**
+ * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
+ * request tunneled through POST. Default to '_method'.
+ * @see getMethod()
+ * @see getBodyParams()
+ */
+ public $methodParam = '_method';
+ /**
+ * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
+ * The array keys are the request `Content-Types`, and the array values are the
+ * corresponding configurations for [[Yii::createObject|creating the parser objects]].
+ * A parser must implement the [[RequestParserInterface]].
+ *
+ * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
+ *
+ * ```
+ * [
+ * 'application/json' => 'yii\web\JsonParser',
+ * ]
+ * ```
+ *
+ * To register a parser for parsing all request types you can use `'*'` as the array key.
+ * This one will be used as a fallback in case no other types match.
+ *
+ * @see getBodyParams()
+ */
+ public $parsers = [];
+
+ /**
+ * @var CookieCollection Collection of request cookies.
+ */
+ private $_cookies;
+ /**
+ * @var array the headers in this collection (indexed by the header names)
+ */
+ private $_headers;
+
+ /**
+ * Resolves the current request into a route and the associated parameters.
+ * @return array the first element is the route, and the second is the associated parameters.
+ * @throws HttpException if the request cannot be resolved.
+ */
+ public function resolve()
+ {
+ $result = Yii::$app->getUrlManager()->parseRequest($this);
+ if ($result !== false) {
+ list ($route, $params) = $result;
+ $_GET = array_merge($_GET, $params);
+
+ return [$route, $_GET];
+ } else {
+ throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
+ }
+ }
+
+ /**
+ * Returns the header collection.
+ * The header collection contains incoming HTTP headers.
+ * @return HeaderCollection the header collection
+ */
+ public function getHeaders()
+ {
+ if ($this->_headers === null) {
+ $this->_headers = new HeaderCollection;
+ if (function_exists('getallheaders')) {
+ $headers = getallheaders();
+ } elseif (function_exists('http_get_request_headers')) {
+ $headers = http_get_request_headers();
+ } else {
+ foreach ($_SERVER as $name => $value) {
+ if (strncmp($name, 'HTTP_', 5) === 0) {
+ $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
+ $this->_headers->add($name, $value);
+ }
+ }
+
+ return $this->_headers;
+ }
+ foreach ($headers as $name => $value) {
+ $this->_headers->add($name, $value);
+ }
+ }
+
+ return $this->_headers;
+ }
+
+ /**
+ * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
+ * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
+ * The value returned is turned into upper case.
+ */
+ public function getMethod()
+ {
+ if (isset($_POST[$this->methodParam])) {
+ return strtoupper($_POST[$this->methodParam]);
+ } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
+ return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
+ } else {
+ return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
+ }
+ }
+
+ /**
+ * Returns whether this is a GET request.
+ * @return boolean whether this is a GET request.
+ */
+ public function getIsGet()
+ {
+ return $this->getMethod() === 'GET';
+ }
+
+ /**
+ * Returns whether this is an OPTIONS request.
+ * @return boolean whether this is a OPTIONS request.
+ */
+ public function getIsOptions()
+ {
+ return $this->getMethod() === 'OPTIONS';
+ }
+
+ /**
+ * Returns whether this is a HEAD request.
+ * @return boolean whether this is a HEAD request.
+ */
+ public function getIsHead()
+ {
+ return $this->getMethod() === 'HEAD';
+ }
+
+ /**
+ * Returns whether this is a POST request.
+ * @return boolean whether this is a POST request.
+ */
+ public function getIsPost()
+ {
+ return $this->getMethod() === 'POST';
+ }
+
+ /**
+ * Returns whether this is a DELETE request.
+ * @return boolean whether this is a DELETE request.
+ */
+ public function getIsDelete()
+ {
+ return $this->getMethod() === 'DELETE';
+ }
+
+ /**
+ * Returns whether this is a PUT request.
+ * @return boolean whether this is a PUT request.
+ */
+ public function getIsPut()
+ {
+ return $this->getMethod() === 'PUT';
+ }
+
+ /**
+ * Returns whether this is a PATCH request.
+ * @return boolean whether this is a PATCH request.
+ */
+ public function getIsPatch()
+ {
+ return $this->getMethod() === 'PATCH';
+ }
+
+ /**
+ * Returns whether this is an AJAX (XMLHttpRequest) request.
+ * @return boolean whether this is an AJAX (XMLHttpRequest) request.
+ */
+ public function getIsAjax()
+ {
+ return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
+ }
+
+ /**
+ * Returns whether this is a PJAX request
+ * @return boolean whether this is a PJAX request
+ */
+ public function getIsPjax()
+ {
+ return $this->getIsAjax() && !empty($_SERVER['HTTP_X_PJAX']);
+ }
+
+ /**
+ * Returns whether this is an Adobe Flash or Flex request.
+ * @return boolean whether this is an Adobe Flash or Adobe Flex request.
+ */
+ public function getIsFlash()
+ {
+ return isset($_SERVER['HTTP_USER_AGENT']) &&
+ (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false);
+ }
+
+ private $_rawBody;
+
+ /**
+ * Returns the raw HTTP request body.
+ * @return string the request body
+ */
+ public function getRawBody()
+ {
+ if ($this->_rawBody === null) {
+ $this->_rawBody = file_get_contents('php://input');
+ }
+
+ return $this->_rawBody;
+ }
+
+ private $_bodyParams;
+
+ /**
+ * Returns the request parameters given in the request body.
+ *
+ * Request parameters are determined using the parsers configured in [[parsers]] property.
+ * If no parsers are configured for the current [[contentType]] it uses the PHP function [[mb_parse_str()]]
+ * to parse the [[rawBody|request body]].
+ * @return array the request parameters given in the request body.
+ * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
+ * @see getMethod()
+ * @see getBodyParam()
+ * @see setBodyParams()
+ */
+ public function getBodyParams()
+ {
+ if ($this->_bodyParams === null) {
+ $contentType = $this->getContentType();
+ if (isset($_POST[$this->methodParam])) {
+ $this->_bodyParams = $_POST;
+ unset($this->_bodyParams[$this->methodParam]);
+ } elseif (isset($this->parsers[$contentType])) {
+ $parser = Yii::createObject($this->parsers[$contentType]);
+ if (!($parser instanceof RequestParserInterface)) {
+ throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
+ }
+ $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType);
+ } elseif (isset($this->parsers['*'])) {
+ $parser = Yii::createObject($this->parsers['*']);
+ if (!($parser instanceof RequestParserInterface)) {
+ throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
+ }
+ $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType);
+ } elseif ($this->getMethod() === 'POST') {
+ // PHP has already parsed the body so we have all params in $_POST
+ $this->_bodyParams = $_POST;
+ } else {
+ $this->_bodyParams = [];
+ mb_parse_str($this->getRawBody(), $this->_bodyParams);
+ }
+ }
+
+ return $this->_bodyParams;
+ }
+
+ /**
+ * Sets the request body parameters.
+ * @param array $values the request body parameters (name-value pairs)
+ * @see getBodyParam()
+ * @see getBodyParams()
+ */
+ public function setBodyParams($values)
+ {
+ $this->_bodyParams = $values;
+ }
+
+ /**
+ * Returns the named request body parameter value.
+ * @param string $name the parameter name
+ * @param mixed $defaultValue the default parameter value if the parameter does not exist.
+ * @return mixed the parameter value
+ * @see getBodyParams()
+ * @see setBodyParams()
+ */
+ public function getBodyParam($name, $defaultValue = null)
+ {
+ $params = $this->getBodyParams();
+
+ return isset($params[$name]) ? $params[$name] : $defaultValue;
+ }
+
+ /**
+ * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
+ *
+ * @param string $name the parameter name
+ * @param mixed $defaultValue the default parameter value if the parameter does not exist.
+ * @return array|mixed
+ */
+ public function post($name = null, $defaultValue = null)
+ {
+ if ($name === null) {
+ return $this->getBodyParams();
+ } else {
+ return $this->getBodyParam($name, $defaultValue);
+ }
+ }
+
+ private $_queryParams;
+
+ /**
+ * Returns the request parameters given in the [[queryString]].
+ *
+ * This method will return the contents of `$_GET` if params where not explicitly set.
+ * @return array the request GET parameter values.
+ * @see setQueryParams()
+ */
+ public function getQueryParams()
+ {
+ if ($this->_queryParams === null) {
+ return $_GET;
+ }
+
+ return $this->_queryParams;
+ }
+
+ /**
+ * Sets the request [[queryString]] parameters.
+ * @param array $values the request query parameters (name-value pairs)
+ * @see getQueryParam()
+ * @see getQueryParams()
+ */
+ public function setQueryParams($values)
+ {
+ $this->_queryParams = $values;
+ }
+
+ /**
+ * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
+ *
+ * @param string $name the parameter name
+ * @param mixed $defaultValue the default parameter value if the parameter does not exist.
+ * @return array|mixed
+ */
+ public function get($name = null, $defaultValue = null)
+ {
+ if ($name === null) {
+ return $this->getQueryParams();
+ } else {
+ return $this->getQueryParam($name, $defaultValue);
+ }
+ }
+
+ /**
+ * Returns the named GET parameter value.
+ * If the GET parameter does not exist, the second parameter to this method will be returned.
+ * @param string $name the GET parameter name. If not specified, whole $_GET is returned.
+ * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
+ * @return mixed the GET parameter value
+ * @see getBodyParam()
+ */
+ public function getQueryParam($name, $defaultValue = null)
+ {
+ $params = $this->getQueryParams();
+
+ return isset($params[$name]) ? $params[$name] : $defaultValue;
+ }
+
+ private $_hostInfo;
+
+ /**
+ * Returns the schema and host part of the current request URL.
+ * The returned URL does not have an ending slash.
+ * By default this is determined based on the user request information.
+ * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
+ * @return string schema and hostname part (with port number if needed) of the request URL (e.g. `http://www.yiiframework.com`)
+ * @see setHostInfo()
+ */
+ public function getHostInfo()
+ {
+ if ($this->_hostInfo === null) {
+ $secure = $this->getIsSecureConnection();
+ $http = $secure ? 'https' : 'http';
+ if (isset($_SERVER['HTTP_HOST'])) {
+ $this->_hostInfo = $http . '://' . $_SERVER['HTTP_HOST'];
+ } else {
+ $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
+ $port = $secure ? $this->getSecurePort() : $this->getPort();
+ if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
+ $this->_hostInfo .= ':' . $port;
+ }
+ }
+ }
+
+ return $this->_hostInfo;
+ }
+
+ /**
+ * Sets the schema and host part of the application URL.
+ * This setter is provided in case the schema and hostname cannot be determined
+ * on certain Web servers.
+ * @param string $value the schema and host part of the application URL. The trailing slashes will be removed.
+ */
+ public function setHostInfo($value)
+ {
+ $this->_hostInfo = rtrim($value, '/');
+ }
+
+ private $_baseUrl;
+
+ /**
+ * Returns the relative URL for the application.
+ * This is similar to [[scriptUrl]] except that it does not include the script file name,
+ * and the ending slashes are removed.
+ * @return string the relative URL for the application
+ * @see setScriptUrl()
+ */
+ public function getBaseUrl()
+ {
+ if ($this->_baseUrl === null) {
+ $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
+ }
+
+ return $this->_baseUrl;
+ }
+
+ /**
+ * Sets the relative URL for the application.
+ * By default the URL is determined based on the entry script URL.
+ * This setter is provided in case you want to change this behavior.
+ * @param string $value the relative URL for the application
+ */
+ public function setBaseUrl($value)
+ {
+ $this->_baseUrl = $value;
+ }
+
+ private $_scriptUrl;
+
+ /**
+ * Returns the relative URL of the entry script.
+ * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
+ * @return string the relative URL of the entry script.
+ * @throws InvalidConfigException if unable to determine the entry script URL
+ */
+ public function getScriptUrl()
+ {
+ if ($this->_scriptUrl === null) {
+ $scriptFile = $this->getScriptFile();
+ $scriptName = basename($scriptFile);
+ if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
+ $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
+ } elseif (basename($_SERVER['PHP_SELF']) === $scriptName) {
+ $this->_scriptUrl = $_SERVER['PHP_SELF'];
+ } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
+ $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
+ } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
+ $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
+ } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
+ $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
+ } else {
+ throw new InvalidConfigException('Unable to determine the entry script URL.');
+ }
+ }
+
+ return $this->_scriptUrl;
+ }
+
+ /**
+ * Sets the relative URL for the application entry script.
+ * This setter is provided in case the entry script URL cannot be determined
+ * on certain Web servers.
+ * @param string $value the relative URL for the application entry script.
+ */
+ public function setScriptUrl($value)
+ {
+ $this->_scriptUrl = '/' . trim($value, '/');
+ }
+
+ private $_scriptFile;
+
+ /**
+ * Returns the entry script file path.
+ * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
+ * @return string the entry script file path
+ */
+ public function getScriptFile()
+ {
+ return isset($this->_scriptFile) ? $this->_scriptFile : $_SERVER['SCRIPT_FILENAME'];
+ }
+
+ /**
+ * Sets the entry script file path.
+ * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
+ * If your server configuration does not return the correct value, you may configure
+ * this property to make it right.
+ * @param string $value the entry script file path.
+ */
+ public function setScriptFile($value)
+ {
+ $this->_scriptFile = $value;
+ }
+
+ private $_pathInfo;
+
+ /**
+ * Returns the path info of the currently requested URL.
+ * A path info refers to the part that is after the entry script and before the question mark (query string).
+ * The starting and ending slashes are both removed.
+ * @return string part of the request URL that is after the entry script and before the question mark.
+ * Note, the returned path info is already URL-decoded.
+ * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
+ */
+ public function getPathInfo()
+ {
+ if ($this->_pathInfo === null) {
+ $this->_pathInfo = $this->resolvePathInfo();
+ }
+
+ return $this->_pathInfo;
+ }
+
+ /**
+ * Sets the path info of the current request.
+ * This method is mainly provided for testing purpose.
+ * @param string $value the path info of the current request
+ */
+ public function setPathInfo($value)
+ {
+ $this->_pathInfo = ltrim($value, '/');
+ }
+
+ /**
+ * Resolves the path info part of the currently requested URL.
+ * A path info refers to the part that is after the entry script and before the question mark (query string).
+ * The starting slashes are both removed (ending slashes will be kept).
+ * @return string part of the request URL that is after the entry script and before the question mark.
+ * Note, the returned path info is decoded.
+ * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
+ */
+ protected function resolvePathInfo()
+ {
+ $pathInfo = $this->getUrl();
+
+ if (($pos = strpos($pathInfo, '?')) !== false) {
+ $pathInfo = substr($pathInfo, 0, $pos);
+ }
+
+ $pathInfo = urldecode($pathInfo);
+
+ // try to encode in UTF8 if not so
+ // http://w3.org/International/questions/qa-forms-utf-8.html
+ if (!preg_match('%^(?:
+ [\x09\x0A\x0D\x20-\x7E] # ASCII
+ | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
+ | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
+ | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
+ | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
+ | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
+ | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
+ | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
+ )*$%xs', $pathInfo)) {
+ $pathInfo = utf8_encode($pathInfo);
+ }
+
+ $scriptUrl = $this->getScriptUrl();
+ $baseUrl = $this->getBaseUrl();
+ if (strpos($pathInfo, $scriptUrl) === 0) {
+ $pathInfo = substr($pathInfo, strlen($scriptUrl));
+ } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
+ $pathInfo = substr($pathInfo, strlen($baseUrl));
+ } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
+ $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
+ } else {
+ throw new InvalidConfigException('Unable to determine the path info of the current request.');
+ }
+
+ if ($pathInfo[0] === '/') {
+ $pathInfo = substr($pathInfo, 1);
+ }
+
+ return (string) $pathInfo;
+ }
+
+ /**
+ * Returns the currently requested absolute URL.
+ * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
+ * @return string the currently requested absolute URL.
+ */
+ public function getAbsoluteUrl()
+ {
+ return $this->getHostInfo() . $this->getUrl();
+ }
+
+ private $_url;
+
+ /**
+ * Returns the currently requested relative URL.
+ * This refers to the portion of the URL that is after the [[hostInfo]] part.
+ * It includes the [[queryString]] part if any.
+ * @return string the currently requested relative URL. Note that the URI returned is URL-encoded.
+ * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
+ */
+ public function getUrl()
+ {
+ if ($this->_url === null) {
+ $this->_url = $this->resolveRequestUri();
+ }
+
+ return $this->_url;
+ }
+
+ /**
+ * Sets the currently requested relative URL.
+ * The URI must refer to the portion that is after [[hostInfo]].
+ * Note that the URI should be URL-encoded.
+ * @param string $value the request URI to be set
+ */
+ public function setUrl($value)
+ {
+ $this->_url = $value;
+ }
+
+ /**
+ * Resolves the request URI portion for the currently requested URL.
+ * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
+ * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
+ * @return string|boolean the request URI portion for the currently requested URL.
+ * Note that the URI returned is URL-encoded.
+ * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
+ */
+ protected function resolveRequestUri()
+ {
+ if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS
+ $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
+ } elseif (isset($_SERVER['REQUEST_URI'])) {
+ $requestUri = $_SERVER['REQUEST_URI'];
+ if ($requestUri !== '' && $requestUri[0] !== '/') {
+ $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
+ }
+ } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
+ $requestUri = $_SERVER['ORIG_PATH_INFO'];
+ if (!empty($_SERVER['QUERY_STRING'])) {
+ $requestUri .= '?' . $_SERVER['QUERY_STRING'];
+ }
+ } else {
+ throw new InvalidConfigException('Unable to determine the request URI.');
+ }
+
+ return $requestUri;
+ }
+
+ /**
+ * Returns part of the request URL that is after the question mark.
+ * @return string part of the request URL that is after the question mark
+ */
+ public function getQueryString()
+ {
+ return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
+ }
+
+ /**
+ * Return if the request is sent via secure channel (https).
+ * @return boolean if the request is sent via secure channel (https)
+ */
+ public function getIsSecureConnection()
+ {
+ return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
+ || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
+ }
+
+ /**
+ * Returns the server name.
+ * @return string server name
+ */
+ public function getServerName()
+ {
+ return $_SERVER['SERVER_NAME'];
+ }
+
+ /**
+ * Returns the server port number.
+ * @return integer server port number
+ */
+ public function getServerPort()
+ {
+ return (int) $_SERVER['SERVER_PORT'];
+ }
+
+ /**
+ * Returns the URL referrer, null if not present
+ * @return string URL referrer, null if not present
+ */
+ public function getReferrer()
+ {
+ return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
+ }
+
+ /**
+ * Returns the user agent, null if not present.
+ * @return string user agent, null if not present
+ */
+ public function getUserAgent()
+ {
+ return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
+ }
+
+ /**
+ * Returns the user IP address.
+ * @return string user IP address
+ */
+ public function getUserIP()
+ {
+ return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
+ }
+
+ /**
+ * Returns the user host name, null if it cannot be determined.
+ * @return string user host name, null if cannot be determined
+ */
+ public function getUserHost()
+ {
+ return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
+ }
+
+ /**
+ * @return string the username sent via HTTP authentication, null if the username is not given
+ */
+ public function getAuthUser()
+ {
+ return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
+ }
+
+ /**
+ * @return string the password sent via HTTP authentication, null if the password is not given
+ */
+ public function getAuthPassword()
+ {
+ return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
+ }
+
+ private $_port;
+
+ /**
+ * Returns the port to use for insecure requests.
+ * Defaults to 80, or the port specified by the server if the current
+ * request is insecure.
+ * @return integer port number for insecure requests.
+ * @see setPort()
+ */
+ public function getPort()
+ {
+ if ($this->_port === null) {
+ $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80;
+ }
+
+ return $this->_port;
+ }
+
+ /**
+ * Sets the port to use for insecure requests.
+ * This setter is provided in case a custom port is necessary for certain
+ * server configurations.
+ * @param integer $value port number.
+ */
+ public function setPort($value)
+ {
+ if ($value != $this->_port) {
+ $this->_port = (int) $value;
+ $this->_hostInfo = null;
+ }
+ }
+
+ private $_securePort;
+
+ /**
+ * Returns the port to use for secure requests.
+ * Defaults to 443, or the port specified by the server if the current
+ * request is secure.
+ * @return integer port number for secure requests.
+ * @see setSecurePort()
+ */
+ public function getSecurePort()
+ {
+ if ($this->_securePort === null) {
+ $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443;
+ }
+
+ return $this->_securePort;
+ }
+
+ /**
+ * Sets the port to use for secure requests.
+ * This setter is provided in case a custom port is necessary for certain
+ * server configurations.
+ * @param integer $value port number.
+ */
+ public function setSecurePort($value)
+ {
+ if ($value != $this->_securePort) {
+ $this->_securePort = (int) $value;
+ $this->_hostInfo = null;
+ }
+ }
+
+ private $_contentTypes;
+
+ /**
+ * Returns the content types acceptable by the end user.
+ * This is determined by the `Accept` HTTP header. For example,
+ *
+ * ```php
+ * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
+ * $types = $request->getAcceptableContentTypes();
+ * print_r($types);
+ * // displays:
+ * // [
+ * // 'application/json' => ['q' => 1, 'version' => '1.0'],
+ * // 'application/xml' => ['q' => 1, 'version' => '2.0'],
+ * // 'text/plain' => ['q' => 0.5],
+ * // ]
+ * ```
+ *
+ * @return array the content types ordered by the quality score. Types with the highest scores
+ * will be returned first. The array keys are the content types, while the array values
+ * are the corresponding quality score and other parameters as given in the header.
+ */
+ public function getAcceptableContentTypes()
+ {
+ if ($this->_contentTypes === null) {
+ if (isset($_SERVER['HTTP_ACCEPT'])) {
+ $this->_contentTypes = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT']);
+ } else {
+ $this->_contentTypes = [];
+ }
+ }
+
+ return $this->_contentTypes;
+ }
+
+ /**
+ * Sets the acceptable content types.
+ * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
+ * @param array $value the content types that are acceptable by the end user. They should
+ * be ordered by the preference level.
+ * @see getAcceptableContentTypes()
+ * @see parseAcceptHeader()
+ */
+ public function setAcceptableContentTypes($value)
+ {
+ $this->_contentTypes = $value;
+ }
+
+ /**
+ * Returns request content-type
+ * The Content-Type header field indicates the MIME type of the data
+ * contained in [[getRawBody()]] or, in the case of the HEAD method, the
+ * media type that would have been sent had the request been a GET.
+ * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
+ * @return string request content-type. Null is returned if this information is not available.
+ * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
+ * HTTP 1.1 header field definitions
+ */
+ public function getContentType()
+ {
+ if (isset($_SERVER["CONTENT_TYPE"])) {
+ return $_SERVER["CONTENT_TYPE"];
+ } elseif (isset($_SERVER["HTTP_CONTENT_TYPE"])) { //fix bug https://bugs.php.net/bug.php?id=66606
+
+ return $_SERVER["HTTP_CONTENT_TYPE"];
+ }
+
+ return null;
+ }
+
+ private $_languages;
+
+ /**
+ * Returns the languages acceptable by the end user.
+ * This is determined by the `Accept-Language` HTTP header.
+ * @return array the languages ordered by the preference level. The first element
+ * represents the most preferred language.
+ */
+ public function getAcceptableLanguages()
+ {
+ if ($this->_languages === null) {
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ $this->_languages = array_keys($this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']));
+ } else {
+ $this->_languages = [];
+ }
+ }
+
+ return $this->_languages;
+ }
+
+ /**
+ * @param array $value the languages that are acceptable by the end user. They should
+ * be ordered by the preference level.
+ */
+ public function setAcceptableLanguages($value)
+ {
+ $this->_languages = $value;
+ }
+
+ /**
+ * Parses the given `Accept` (or `Accept-Language`) header.
+ *
+ * This method will return the acceptable values with their quality scores and the corresponding parameters
+ * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
+ * while the array values consisting of the corresponding quality scores and parameters. The acceptable
+ * values with the highest quality scores will be returned first. For example,
+ *
+ * ```php
+ * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
+ * $accepts = $request->parseAcceptHeader($header);
+ * print_r($accepts);
+ * // displays:
+ * // [
+ * // 'application/json' => ['q' => 1, 'version' => '1.0'],
+ * // 'application/xml' => ['q' => 1, 'version' => '2.0'],
+ * // 'text/plain' => ['q' => 0.5],
+ * // ]
+ * ```
+ *
+ * @param string $header the header to be parsed
+ * @return array the acceptable values ordered by their quality score. The values with the highest scores
+ * will be returned first.
+ */
+ public function parseAcceptHeader($header)
+ {
+ $accepts = [];
+ foreach (explode(',', $header) as $i => $part) {
+ $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
+ if (empty($params)) {
+ continue;
+ }
+ $values = [
+ 'q' => [$i, array_shift($params), 1],
+ ];
+ foreach ($params as $param) {
+ if (strpos($param, '=') !== false) {
+ list ($key, $value) = explode('=', $param, 2);
+ if ($key === 'q') {
+ $values['q'][2] = (double) $value;
+ } else {
+ $values[$key] = $value;
+ }
+ } else {
+ $values[] = $param;
+ }
+ }
+ $accepts[] = $values;
+ }
+
+ usort($accepts, function ($a, $b) {
+ $a = $a['q']; // index, name, q
+ $b = $b['q'];
+ if ($a[2] > $b[2]) {
+ return -1;
+ } elseif ($a[2] < $b[2]) {
+ return 1;
+ } elseif ($a[1] === $b[1]) {
+ return $a[0] > $b[0] ? 1 : -1;
+ } elseif ($a[1] === '*/*') {
+ return 1;
+ } elseif ($b[1] === '*/*') {
+ return -1;
+ } else {
+ $wa = $a[1][strlen($a[1]) - 1] === '*';
+ $wb = $b[1][strlen($b[1]) - 1] === '*';
+ if ($wa xor $wb) {
+ return $wa ? 1 : -1;
+ } else {
+ return $a[0] > $b[0] ? 1 : -1;
+ }
+ }
+ });
+
+ $result = [];
+ foreach ($accepts as $accept) {
+ $name = $accept['q'][1];
+ $accept['q'] = $accept['q'][2];
+ $result[$name] = $accept;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the user-preferred language that should be used by this application.
+ * The language resolution is based on the user preferred languages and the languages
+ * supported by the application. The method will try to find the best match.
+ * @param array $languages a list of the languages supported by the application. If this is empty, the current
+ * application language will be returned without further processing.
+ * @return string the language that the application should use.
+ */
+ public function getPreferredLanguage(array $languages = [])
+ {
+ if (empty($languages)) {
+ return Yii::$app->language;
+ }
+ foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
+ $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
+ foreach ($languages as $language) {
+ $language = str_replace('_', '-', strtolower($language));
+ // en-us==en-us, en==en-us, en-us==en
+ if ($language === $acceptableLanguage || strpos($acceptableLanguage, $language . '-') === 0 || strpos($language, $acceptableLanguage . '-') === 0) {
+ return $language;
+ }
+ }
+ }
+
+ return reset($languages);
+ }
+
+ /**
+ * Returns the cookie collection.
+ * Through the returned cookie collection, you may access a cookie using the following syntax:
+ *
+ * ~~~
+ * $cookie = $request->cookies['name']
+ * if ($cookie !== null) {
+ * $value = $cookie->value;
+ * }
+ *
+ * // alternatively
+ * $value = $request->cookies->getValue('name');
+ * ~~~
+ *
+ * @return CookieCollection the cookie collection.
+ */
+ public function getCookies()
+ {
+ if ($this->_cookies === null) {
+ $this->_cookies = new CookieCollection($this->loadCookies(), [
+ 'readOnly' => true,
+ ]);
+ }
+
+ return $this->_cookies;
+ }
+
+ /**
+ * Converts `$_COOKIE` into an array of [[Cookie]].
+ * @return array the cookies obtained from request
+ */
+ protected function loadCookies()
+ {
+ $cookies = [];
+ if ($this->enableCookieValidation) {
+ $key = $this->getCookieValidationKey();
+ foreach ($_COOKIE as $name => $value) {
+ if (is_string($value) && ($value = Security::validateData($value, $key)) !== false) {
+ $cookies[$name] = new Cookie([
+ 'name' => $name,
+ 'value' => @unserialize($value),
+ ]);
+ }
+ }
+ } else {
+ foreach ($_COOKIE as $name => $value) {
+ $cookies[$name] = new Cookie([
+ 'name' => $name,
+ 'value' => $value,
+ ]);
+ }
+ }
+
+ return $cookies;
+ }
+
+ private $_cookieValidationKey;
+
+ /**
+ * @return string the secret key used for cookie validation. If it was not set previously,
+ * a random key will be generated and used.
+ */
+ public function getCookieValidationKey()
+ {
+ if ($this->_cookieValidationKey === null) {
+ $this->_cookieValidationKey = Security::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
+ }
+
+ return $this->_cookieValidationKey;
+ }
+
+ /**
+ * Sets the secret key used for cookie validation.
+ * @param string $value the secret key used for cookie validation.
+ */
+ public function setCookieValidationKey($value)
+ {
+ $this->_cookieValidationKey = $value;
+ }
+
+ /**
+ * @var Cookie
+ */
+ private $_csrfCookie;
+
+ /**
+ * Returns the unmasked random token used to perform CSRF validation.
+ * This token is typically sent via a cookie. If such a cookie does not exist, a new token will be generated.
+ * @return string the random token for CSRF validation.
+ * @see enableCsrfValidation
+ */
+ public function getRawCsrfToken()
+ {
+ if ($this->_csrfCookie === null) {
+ $this->_csrfCookie = $this->getCookies()->get($this->csrfParam);
+ if ($this->_csrfCookie === null) {
+ $this->_csrfCookie = $this->createCsrfCookie();
+ Yii::$app->getResponse()->getCookies()->add($this->_csrfCookie);
+ }
+ }
+
+ return $this->_csrfCookie->value;
+ }
+
+ private $_csrfToken;
+
+ /**
+ * Returns the token used to perform CSRF validation.
+ *
+ * This token is a masked version of [[rawCsrfToken]] to prevent [BREACH attacks](http://breachattack.com/).
+ * This token may be passed along via a hidden field of an HTML form or an HTTP header value
+ * to support CSRF validation.
+ *
+ * @return string the token used to perform CSRF validation.
+ */
+ public function getCsrfToken()
+ {
+ if ($this->_csrfToken === null) {
+ // the mask doesn't need to be very random
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.';
+ $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, self::CSRF_MASK_LENGTH);
+
+ $token = $this->getRawCsrfToken();
+ // The + sign may be decoded as blank space later, which will fail the validation
+ $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
+ }
+
+ return $this->_csrfToken;
+ }
+
+ /**
+ * Returns the XOR result of two strings.
+ * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one.
+ * @param string $token1
+ * @param string $token2
+ * @return string the XOR result
+ */
+ private function xorTokens($token1, $token2)
+ {
+ $n1 = StringHelper::byteLength($token1);
+ $n2 = StringHelper::byteLength($token2);
+ if ($n1 > $n2) {
+ $token2 = str_pad($token2, $n1, $token2);
+ } elseif ($n1 < $n2) {
+ $token1 = str_pad($token1, $n2, $token1);
+ }
+
+ return $token1 ^ $token2;
+ }
+
+ /**
+ * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
+ */
+ public function getCsrfTokenFromHeader()
+ {
+ $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER));
+
+ return isset($_SERVER[$key]) ? $_SERVER[$key] : null;
+ }
+
+ /**
+ * Creates a cookie with a randomly generated CSRF token.
+ * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
+ * @return Cookie the generated cookie
+ * @see enableCsrfValidation
+ */
+ protected function createCsrfCookie()
+ {
+ $options = $this->csrfCookie;
+ $options['name'] = $this->csrfParam;
+ $options['value'] = Security::generateRandomKey();
+
+ return new Cookie($options);
+ }
+
+ /**
+ * Performs the CSRF validation.
+ * The method will compare the CSRF token obtained from a cookie and from a POST field.
+ * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised.
+ * This method is called in [[Controller::beforeAction()]].
+ * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
+ */
+ public function validateCsrfToken()
+ {
+ $method = $this->getMethod();
+ // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
+ if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
+ return true;
+ }
+ $trueToken = $this->getCookies()->getValue($this->csrfParam);
+ $token = $this->getBodyParam($this->csrfParam);
+
+ return $this->validateCsrfTokenInternal($token, $trueToken)
+ || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
+ }
+
+ private function validateCsrfTokenInternal($token, $trueToken)
+ {
+ $token = base64_decode(str_replace('.', '+', $token));
+ $n = StringHelper::byteLength($token);
+ if ($n <= self::CSRF_MASK_LENGTH) {
+ return false;
+ }
+ $mask = StringHelper::byteSubstr($token, 0, self::CSRF_MASK_LENGTH);
+ $token = StringHelper::byteSubstr($token, self::CSRF_MASK_LENGTH, $n - self::CSRF_MASK_LENGTH);
+ $token = $this->xorTokens($mask, $token);
+
+ return $token === $trueToken;
+ }
}
diff --git a/framework/web/RequestParserInterface.php b/framework/web/RequestParserInterface.php
index b819b0b964e..371cfba854e 100644
--- a/framework/web/RequestParserInterface.php
+++ b/framework/web/RequestParserInterface.php
@@ -15,11 +15,11 @@
*/
interface RequestParserInterface
{
- /**
- * Parses a HTTP request body.
- * @param string $rawBody the raw HTTP request body.
- * @param string $contentType the content type specified for the request body.
- * @return array parameters parsed from the request body
- */
- public function parse($rawBody, $contentType);
+ /**
+ * Parses a HTTP request body.
+ * @param string $rawBody the raw HTTP request body.
+ * @param string $contentType the content type specified for the request body.
+ * @return array parameters parsed from the request body
+ */
+ public function parse($rawBody, $contentType);
}
diff --git a/framework/web/Response.php b/framework/web/Response.php
index 611cb8eb71c..b540139ee02 100644
--- a/framework/web/Response.php
+++ b/framework/web/Response.php
@@ -60,824 +60,827 @@
*/
class Response extends \yii\base\Response
{
- /**
- * @event ResponseEvent an event that is triggered at the beginning of [[send()]].
- */
- const EVENT_BEFORE_SEND = 'beforeSend';
- /**
- * @event ResponseEvent an event that is triggered at the end of [[send()]].
- */
- const EVENT_AFTER_SEND = 'afterSend';
- /**
- * @event ResponseEvent an event that is triggered right after [[prepare()]] is called in [[send()]].
- * You may respond to this event to filter the response content before it is sent to the client.
- */
- const EVENT_AFTER_PREPARE = 'afterPrepare';
-
- const FORMAT_RAW = 'raw';
- const FORMAT_HTML = 'html';
- const FORMAT_JSON = 'json';
- const FORMAT_JSONP = 'jsonp';
- const FORMAT_XML = 'xml';
-
- /**
- * @var string the response format. This determines how to convert [[data]] into [[content]]
- * when the latter is not set. By default, the following formats are supported:
- *
- * - [[FORMAT_RAW]]: the data will be treated as the response content without any conversion.
- * No extra HTTP header will be added.
- * - [[FORMAT_HTML]]: the data will be treated as the response content without any conversion.
- * The "Content-Type" header will set as "text/html" if it is not set previously.
- * - [[FORMAT_JSON]]: the data will be converted into JSON format, and the "Content-Type"
- * header will be set as "application/json".
- * - [[FORMAT_JSONP]]: the data will be converted into JSONP format, and the "Content-Type"
- * header will be set as "text/javascript". Note that in this case `$data` must be an array
- * with "data" and "callback" elements. The former refers to the actual data to be sent,
- * while the latter refers to the name of the JavaScript callback.
- * - [[FORMAT_XML]]: the data will be converted into XML format. Please refer to [[XmlResponseFormatter]]
- * for more details.
- *
- * You may customize the formatting process or support additional formats by configuring [[formatters]].
- * @see formatters
- */
- public $format = self::FORMAT_HTML;
- /**
- * @var array the formatters for converting data into the response content of the specified [[format]].
- * The array keys are the format names, and the array values are the corresponding configurations
- * for creating the formatter objects.
- * @see format
- */
- public $formatters;
- /**
- * @var mixed the original response data. When this is not null, it will be converted into [[content]]
- * according to [[format]] when the response is being sent out.
- * @see content
- */
- public $data;
- /**
- * @var string the response content. When [[data]] is not null, it will be converted into [[content]]
- * according to [[format]] when the response is being sent out.
- * @see data
- */
- public $content;
- /**
- * @var resource|array the stream to be sent. This can be a stream handle or an array of stream handle,
- * the begin position and the end position. Note that when this property is set, the [[data]] and [[content]]
- * properties will be ignored by [[send()]].
- */
- public $stream;
- /**
- * @var string the charset of the text response. If not set, it will use
- * the value of [[Application::charset]].
- */
- public $charset;
- /**
- * @var string the HTTP status description that comes together with the status code.
- * @see httpStatuses
- */
- public $statusText = 'OK';
- /**
- * @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`,
- * or '1.1' if that is not available.
- */
- public $version;
- /**
- * @var boolean whether the response has been sent. If this is true, calling [[send()]] will do nothing.
- */
- public $isSent = false;
- /**
- * @var array list of HTTP status codes and the corresponding texts
- */
- public static $httpStatuses = [
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 102 => 'Processing',
- 118 => 'Connection timed out',
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 207 => 'Multi-Status',
- 208 => 'Already Reported',
- 210 => 'Content Different',
- 226 => 'IM Used',
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found',
- 303 => 'See Other',
- 304 => 'Not Modified',
- 305 => 'Use Proxy',
- 306 => 'Reserved',
- 307 => 'Temporary Redirect',
- 308 => 'Permanent Redirect',
- 310 => 'Too many Redirect',
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Time-out',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested range unsatisfiable',
- 417 => 'Expectation failed',
- 418 => 'I\'m a teapot',
- 422 => 'Unprocessable entity',
- 423 => 'Locked',
- 424 => 'Method failure',
- 425 => 'Unordered Collection',
- 426 => 'Upgrade Required',
- 428 => 'Precondition Required',
- 429 => 'Too Many Requests',
- 431 => 'Request Header Fields Too Large',
- 449 => 'Retry With',
- 450 => 'Blocked by Windows Parental Controls',
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway ou Proxy Error',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Time-out',
- 505 => 'HTTP Version not supported',
- 507 => 'Insufficient storage',
- 508 => 'Loop Detected',
- 509 => 'Bandwidth Limit Exceeded',
- 510 => 'Not Extended',
- 511 => 'Network Authentication Required',
- ];
-
- /**
- * @var integer the HTTP status code to send with the response.
- */
- private $_statusCode = 200;
- /**
- * @var HeaderCollection
- */
- private $_headers;
-
- /**
- * Initializes this component.
- */
- public function init()
- {
- if ($this->version === null) {
- if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === '1.0') {
- $this->version = '1.0';
- } else {
- $this->version = '1.1';
- }
- }
- if ($this->charset === null) {
- $this->charset = Yii::$app->charset;
- }
- }
-
- /**
- * @return integer the HTTP status code to send with the response.
- */
- public function getStatusCode()
- {
- return $this->_statusCode;
- }
-
- /**
- * Sets the response status code.
- * This method will set the corresponding status text if `$text` is null.
- * @param integer $value the status code
- * @param string $text the status text. If not set, it will be set automatically based on the status code.
- * @throws InvalidParamException if the status code is invalid.
- */
- public function setStatusCode($value, $text = null)
- {
- if ($value === null) {
- $value = 200;
- }
- $this->_statusCode = (int)$value;
- if ($this->getIsInvalid()) {
- throw new InvalidParamException("The HTTP status code is invalid: $value");
- }
- if ($text === null) {
- $this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : '';
- } else {
- $this->statusText = $text;
- }
- }
-
- /**
- * Returns the header collection.
- * The header collection contains the currently registered HTTP headers.
- * @return HeaderCollection the header collection
- */
- public function getHeaders()
- {
- if ($this->_headers === null) {
- $this->_headers = new HeaderCollection;
- }
- return $this->_headers;
- }
-
- /**
- * Sends the response to the client.
- */
- public function send()
- {
- if ($this->isSent) {
- return;
- }
- $this->trigger(self::EVENT_BEFORE_SEND);
- $this->prepare();
- $this->trigger(self::EVENT_AFTER_PREPARE);
- $this->sendHeaders();
- $this->sendContent();
- $this->trigger(self::EVENT_AFTER_SEND);
- $this->isSent = true;
- }
-
- /**
- * Clears the headers, cookies, content, status code of the response.
- */
- public function clear()
- {
- $this->_headers = null;
- $this->_cookies = null;
- $this->_statusCode = 200;
- $this->statusText = 'OK';
- $this->data = null;
- $this->stream = null;
- $this->content = null;
- $this->isSent = false;
- }
-
- /**
- * Sends the response headers to the client
- */
- protected function sendHeaders()
- {
- if (headers_sent()) {
- return;
- }
- $statusCode = $this->getStatusCode();
- header("HTTP/{$this->version} $statusCode {$this->statusText}");
- if ($this->_headers) {
- $headers = $this->getHeaders();
- foreach ($headers as $name => $values) {
- $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
- foreach ($values as $value) {
- header("$name: $value", false);
- }
- }
- }
- $this->sendCookies();
- }
-
- /**
- * Sends the cookies to the client.
- */
- protected function sendCookies()
- {
- if ($this->_cookies === null) {
- return;
- }
- $request = Yii::$app->getRequest();
- if ($request->enableCookieValidation) {
- $validationKey = $request->getCookieValidationKey();
- }
- foreach ($this->getCookies() as $cookie) {
- $value = $cookie->value;
- if ($cookie->expire != 1 && isset($validationKey)) {
- $value = Security::hashData(serialize($value), $validationKey);
- }
- setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
- }
- $this->getCookies()->removeAll();
- }
-
- /**
- * Sends the response content to the client
- */
- protected function sendContent()
- {
- if ($this->stream === null) {
- echo $this->content;
- return;
- }
-
- set_time_limit(0); // Reset time limit for big files
- $chunkSize = 8 * 1024 * 1024; // 8MB per chunk
-
- if (is_array($this->stream)) {
- list ($handle, $begin, $end) = $this->stream;
- fseek($handle, $begin);
- while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
- if ($pos + $chunkSize > $end) {
- $chunkSize = $end - $pos + 1;
- }
- echo fread($handle, $chunkSize);
- flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
- }
- fclose($handle);
- } else {
- while (!feof($this->stream)) {
- echo fread($this->stream, $chunkSize);
- flush();
- }
- fclose($this->stream);
- }
- }
-
- /**
- * Sends a file to the browser.
- *
- * Note that this method only prepares the response for file sending. The file is not sent
- * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
- *
- * @param string $filePath the path of the file to be sent.
- * @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`.
- * @param string $mimeType the MIME type of the content. If null, it will be guessed based on `$filePath`
- * @return static the response object itself
- */
- public function sendFile($filePath, $attachmentName = null, $mimeType = null)
- {
- if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
- $mimeType = 'application/octet-stream';
- }
- if ($attachmentName === null) {
- $attachmentName = basename($filePath);
- }
- $handle = fopen($filePath, 'rb');
- $this->sendStreamAsFile($handle, $attachmentName, $mimeType);
-
- return $this;
- }
-
- /**
- * Sends the specified content as a file to the browser.
- *
- * Note that this method only prepares the response for file sending. The file is not sent
- * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
- *
- * @param string $content the content to be sent. The existing [[content]] will be discarded.
- * @param string $attachmentName the file name shown to the user.
- * @param string $mimeType the MIME type of the content.
- * @return static the response object itself
- * @throws HttpException if the requested range is not satisfiable
- */
- public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream')
- {
- $headers = $this->getHeaders();
- $contentLength = StringHelper::byteLength($content);
- $range = $this->getHttpRange($contentLength);
- if ($range === false) {
- $headers->set('Content-Range', "bytes */$contentLength");
- throw new HttpException(416, 'Requested range not satisfiable');
- }
-
- $headers->setDefault('Pragma', 'public')
- ->setDefault('Accept-Ranges', 'bytes')
- ->setDefault('Expires', '0')
- ->setDefault('Content-Type', $mimeType)
- ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
- ->setDefault('Content-Transfer-Encoding', 'binary')
- ->setDefault('Content-Length', StringHelper::byteLength($content))
- ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
-
- list($begin, $end) = $range;
- if ($begin !=0 || $end != $contentLength - 1) {
- $this->setStatusCode(206);
- $headers->set('Content-Range', "bytes $begin-$end/$contentLength");
- $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1);
- } else {
- $this->setStatusCode(200);
- $this->content = $content;
- }
-
- $this->format = self::FORMAT_RAW;
-
- return $this;
- }
-
- /**
- * Sends the specified stream as a file to the browser.
- *
- * Note that this method only prepares the response for file sending. The file is not sent
- * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
- *
- * @param resource $handle the handle of the stream to be sent.
- * @param string $attachmentName the file name shown to the user.
- * @param string $mimeType the MIME type of the stream content.
- * @return static the response object itself
- * @throws HttpException if the requested range cannot be satisfied.
- */
- public function sendStreamAsFile($handle, $attachmentName, $mimeType = 'application/octet-stream')
- {
- $headers = $this->getHeaders();
- fseek($handle, 0, SEEK_END);
- $fileSize = ftell($handle);
-
- $range = $this->getHttpRange($fileSize);
- if ($range === false) {
- $headers->set('Content-Range', "bytes */$fileSize");
- throw new HttpException(416, 'Requested range not satisfiable');
- }
-
- list($begin, $end) = $range;
- if ($begin !=0 || $end != $fileSize - 1) {
- $this->setStatusCode(206);
- $headers->set('Content-Range', "bytes $begin-$end/$fileSize");
- } else {
- $this->setStatusCode(200);
- }
-
- $length = $end - $begin + 1;
-
- $headers->setDefault('Pragma', 'public')
- ->setDefault('Accept-Ranges', 'bytes')
- ->setDefault('Expires', '0')
- ->setDefault('Content-Type', $mimeType)
- ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
- ->setDefault('Content-Transfer-Encoding', 'binary')
- ->setDefault('Content-Length', $length)
- ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
- $this->format = self::FORMAT_RAW;
- $this->stream = [$handle, $begin, $end];
-
- return $this;
- }
-
- /**
- * Determines the HTTP range given in the request.
- * @param integer $fileSize the size of the file that will be used to validate the requested HTTP range.
- * @return array|boolean the range (begin, end), or false if the range request is invalid.
- */
- protected function getHttpRange($fileSize)
- {
- if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') {
- return [0, $fileSize - 1];
- }
- if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) {
- return false;
- }
- if ($matches[1] === '') {
- $start = $fileSize - $matches[2];
- $end = $fileSize - 1;
- } elseif ($matches[2] !== '') {
- $start = $matches[1];
- $end = $matches[2];
- if ($end >= $fileSize) {
- $end = $fileSize - 1;
- }
- } else {
- $start = $matches[1];
- $end = $fileSize - 1;
- }
- if ($start < 0 || $start > $end) {
- return false;
- } else {
- return [$start, $end];
- }
- }
-
- /**
- * Sends existing file to a browser as a download using x-sendfile.
- *
- * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver
- * that in turn processes the request, this way eliminating the need to perform tasks like reading the file
- * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great
- * increase in performance as the web application is allowed to terminate earlier while the webserver is
- * handling the request.
- *
- * The request is sent to the server through a special non-standard HTTP-header.
- * When the web server encounters the presence of such header it will discard all output and send the file
- * specified by that header using web server internals including all optimizations like caching-headers.
- *
- * As this header directive is non-standard different directives exists for different web servers applications:
- *
- * - Apache: [X-Sendfile](http://tn123.org/mod_xsendfile)
- * - Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
- * - Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
- * - Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile)
- * - Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile)
- *
- * So for this method to work the X-SENDFILE option/module should be enabled by the web server and
- * a proper xHeader should be sent.
- *
- * **Note**
- *
- * This option allows to download files that are not under web folders, and even files that are otherwise protected
- * (deny from all) like `.htaccess`.
- *
- * **Side effects**
- *
- * If this option is disabled by the web server, when this method is called a download configuration dialog
- * will open but the downloaded file will have 0 bytes.
- *
- * **Known issues**
- *
- * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
- * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site
- * is either unavailable or cannot be found.". You can work around this problem by removing the `Pragma`-header.
- *
- * **Example**
- *
- * ~~~
- * Yii::$app->response->xSendFile('/home/user/Pictures/picture1.jpg');
- * ~~~
- *
- * @param string $filePath file name with full path
- * @param string $mimeType the MIME type of the file. If null, it will be determined based on `$filePath`.
- * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`.
- * @param string $xHeader the name of the x-sendfile header.
- * @return static the response object itself
- */
- public function xSendFile($filePath, $attachmentName = null, $mimeType = null, $xHeader = 'X-Sendfile')
- {
- if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
- $mimeType = 'application/octet-stream';
- }
- if ($attachmentName === null) {
- $attachmentName = basename($filePath);
- }
-
- $this->getHeaders()
- ->setDefault($xHeader, $filePath)
- ->setDefault('Content-Type', $mimeType)
- ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
-
- return $this;
- }
-
- /**
- * Redirects the browser to the specified URL.
- *
- * This method adds a "Location" header to the current response. Note that it does not send out
- * the header until [[send()]] is called. In a controller action you may use this method as follows:
- *
- * ~~~
- * return Yii::$app->getResponse()->redirect($url);
- * ~~~
- *
- * In other places, if you want to send out the "Location" header immediately, you should use
- * the following code:
- *
- * ~~~
- * Yii::$app->getResponse()->redirect($url)->send();
- * return;
- * ~~~
- *
- * In AJAX mode, this normally will not work as expected unless there are some
- * client-side JavaScript code handling the redirection. To help achieve this goal,
- * this method will send out a "X-Redirect" header instead of "Location".
- *
- * If you use the "yii" JavaScript module, it will handle the AJAX redirection as
- * described above. Otherwise, you should write the following JavaScript code to
- * handle the redirection:
- *
- * ~~~
- * $document.ajaxComplete(function (event, xhr, settings) {
- * var url = xhr.getResponseHeader('X-Redirect');
- * if (url) {
- * window.location = url;
- * }
- * });
- * ~~~
- *
- * @param string|array $url the URL to be redirected to. This can be in one of the following formats:
- *
- * - a string representing a URL (e.g. "http://example.com")
- * - a string representing a URL alias (e.g. "@example.com")
- * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`).
- * Note that the route is with respect to the whole application, instead of relative to a controller or module.
- * [[Url::to()]] will be used to convert the array into a URL.
- *
- * Any relative URL will be converted into an absolute one by prepending it with the host info
- * of the current request.
- *
- * @param integer $statusCode the HTTP status code. Defaults to 302.
- * See
- * for details about HTTP status code
- * @return static the response object itself
- */
- public function redirect($url, $statusCode = 302)
- {
- if (is_array($url) && isset($url[0])) {
- // ensure the route is absolute
- $url[0] = '/' . ltrim($url[0], '/');
- }
- $url = Url::to($url);
- if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
- $url = Yii::$app->getRequest()->getHostInfo() . $url;
- }
-
- if (Yii::$app->getRequest()->getIsPjax()) {
- $this->getHeaders()->set('X-Pjax-Url', $url);
- } elseif (Yii::$app->getRequest()->getIsAjax()) {
- $this->getHeaders()->set('X-Redirect', $url);
- } else {
- $this->getHeaders()->set('Location', $url);
- }
- $this->setStatusCode($statusCode);
-
- return $this;
- }
-
- /**
- * Refreshes the current page.
- * The effect of this method call is the same as the user pressing the refresh button of his browser
- * (without re-posting data).
- *
- * In a controller action you may use this method like this:
- *
- * ~~~
- * return Yii::$app->getResponse()->refresh();
- * ~~~
- *
- * @param string $anchor the anchor that should be appended to the redirection URL.
- * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
- * @return Response the response object itself
- */
- public function refresh($anchor = '')
- {
- return $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
- }
-
- private $_cookies;
-
- /**
- * Returns the cookie collection.
- * Through the returned cookie collection, you add or remove cookies as follows,
- *
- * ~~~
- * // add a cookie
- * $response->cookies->add(new Cookie([
- * 'name' => $name,
- * 'value' => $value,
- * ]);
- *
- * // remove a cookie
- * $response->cookies->remove('name');
- * // alternatively
- * unset($response->cookies['name']);
- * ~~~
- *
- * @return CookieCollection the cookie collection.
- */
- public function getCookies()
- {
- if ($this->_cookies === null) {
- $this->_cookies = new CookieCollection;
- }
- return $this->_cookies;
- }
-
- /**
- * @return boolean whether this response has a valid [[statusCode]].
- */
- public function getIsInvalid()
- {
- return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
- }
-
- /**
- * @return boolean whether this response is informational
- */
- public function getIsInformational()
- {
- return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
- }
-
- /**
- * @return boolean whether this response is successful
- */
- public function getIsSuccessful()
- {
- return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
- }
-
- /**
- * @return boolean whether this response is a redirection
- */
- public function getIsRedirection()
- {
- return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
- }
-
- /**
- * @return boolean whether this response indicates a client error
- */
- public function getIsClientError()
- {
- return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
- }
-
- /**
- * @return boolean whether this response indicates a server error
- */
- public function getIsServerError()
- {
- return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
- }
-
- /**
- * @return boolean whether this response is OK
- */
- public function getIsOk()
- {
- return $this->getStatusCode() == 200;
- }
-
- /**
- * @return boolean whether this response indicates the current request is forbidden
- */
- public function getIsForbidden()
- {
- return $this->getStatusCode() == 403;
- }
-
- /**
- * @return boolean whether this response indicates the currently requested resource is not found
- */
- public function getIsNotFound()
- {
- return $this->getStatusCode() == 404;
- }
-
- /**
- * @return boolean whether this response is empty
- */
- public function getIsEmpty()
- {
- return in_array($this->getStatusCode(), [201, 204, 304]);
- }
-
- /**
- * Prepares for sending the response.
- * The default implementation will convert [[data]] into [[content]] and set headers accordingly.
- * @throws InvalidConfigException if the formatter for the specified format is invalid or [[format]] is not supported
- */
- protected function prepare()
- {
- if ($this->stream !== null || $this->data === null) {
- return;
- }
-
- if (isset($this->formatters[$this->format])) {
- $formatter = $this->formatters[$this->format];
- if (!is_object($formatter)) {
- $formatter = Yii::createObject($formatter);
- }
- if ($formatter instanceof ResponseFormatterInterface) {
- $formatter->format($this);
- } else {
- throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
- }
- } else {
- switch ($this->format) {
- case self::FORMAT_HTML:
- $this->getHeaders()->setDefault('Content-Type', 'text/html; charset=' . $this->charset);
- $this->content = $this->data;
- break;
- case self::FORMAT_RAW:
- $this->content = $this->data;
- break;
- case self::FORMAT_JSON:
- $this->getHeaders()->set('Content-Type', 'application/json; charset=UTF-8');
- $this->content = Json::encode($this->data);
- break;
- case self::FORMAT_JSONP:
- $this->getHeaders()->set('Content-Type', 'text/javascript; charset=' . $this->charset);
- if (is_array($this->data) && isset($this->data['data'], $this->data['callback'])) {
- $this->content = sprintf('%s(%s);', $this->data['callback'], Json::encode($this->data['data']));
- } else {
- $this->content = '';
- Yii::warning("The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.", __METHOD__);
- }
- break;
- case self::FORMAT_XML:
- Yii::createObject(XmlResponseFormatter::className())->format($this);
- break;
- default:
- throw new InvalidConfigException("Unsupported response format: {$this->format}");
- }
- }
-
- if (is_array($this->content)) {
- throw new InvalidParamException("Response content must not be an array.");
- } elseif (is_object($this->content)) {
- if (method_exists($this->content, '__toString')) {
- $this->content = $this->content->__toString();
- } else {
- throw new InvalidParamException("Response content must be a string or an object implementing __toString().");
- }
- }
- }
+ /**
+ * @event ResponseEvent an event that is triggered at the beginning of [[send()]].
+ */
+ const EVENT_BEFORE_SEND = 'beforeSend';
+ /**
+ * @event ResponseEvent an event that is triggered at the end of [[send()]].
+ */
+ const EVENT_AFTER_SEND = 'afterSend';
+ /**
+ * @event ResponseEvent an event that is triggered right after [[prepare()]] is called in [[send()]].
+ * You may respond to this event to filter the response content before it is sent to the client.
+ */
+ const EVENT_AFTER_PREPARE = 'afterPrepare';
+
+ const FORMAT_RAW = 'raw';
+ const FORMAT_HTML = 'html';
+ const FORMAT_JSON = 'json';
+ const FORMAT_JSONP = 'jsonp';
+ const FORMAT_XML = 'xml';
+
+ /**
+ * @var string the response format. This determines how to convert [[data]] into [[content]]
+ * when the latter is not set. By default, the following formats are supported:
+ *
+ * - [[FORMAT_RAW]]: the data will be treated as the response content without any conversion.
+ * No extra HTTP header will be added.
+ * - [[FORMAT_HTML]]: the data will be treated as the response content without any conversion.
+ * The "Content-Type" header will set as "text/html" if it is not set previously.
+ * - [[FORMAT_JSON]]: the data will be converted into JSON format, and the "Content-Type"
+ * header will be set as "application/json".
+ * - [[FORMAT_JSONP]]: the data will be converted into JSONP format, and the "Content-Type"
+ * header will be set as "text/javascript". Note that in this case `$data` must be an array
+ * with "data" and "callback" elements. The former refers to the actual data to be sent,
+ * while the latter refers to the name of the JavaScript callback.
+ * - [[FORMAT_XML]]: the data will be converted into XML format. Please refer to [[XmlResponseFormatter]]
+ * for more details.
+ *
+ * You may customize the formatting process or support additional formats by configuring [[formatters]].
+ * @see formatters
+ */
+ public $format = self::FORMAT_HTML;
+ /**
+ * @var array the formatters for converting data into the response content of the specified [[format]].
+ * The array keys are the format names, and the array values are the corresponding configurations
+ * for creating the formatter objects.
+ * @see format
+ */
+ public $formatters;
+ /**
+ * @var mixed the original response data. When this is not null, it will be converted into [[content]]
+ * according to [[format]] when the response is being sent out.
+ * @see content
+ */
+ public $data;
+ /**
+ * @var string the response content. When [[data]] is not null, it will be converted into [[content]]
+ * according to [[format]] when the response is being sent out.
+ * @see data
+ */
+ public $content;
+ /**
+ * @var resource|array the stream to be sent. This can be a stream handle or an array of stream handle,
+ * the begin position and the end position. Note that when this property is set, the [[data]] and [[content]]
+ * properties will be ignored by [[send()]].
+ */
+ public $stream;
+ /**
+ * @var string the charset of the text response. If not set, it will use
+ * the value of [[Application::charset]].
+ */
+ public $charset;
+ /**
+ * @var string the HTTP status description that comes together with the status code.
+ * @see httpStatuses
+ */
+ public $statusText = 'OK';
+ /**
+ * @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`,
+ * or '1.1' if that is not available.
+ */
+ public $version;
+ /**
+ * @var boolean whether the response has been sent. If this is true, calling [[send()]] will do nothing.
+ */
+ public $isSent = false;
+ /**
+ * @var array list of HTTP status codes and the corresponding texts
+ */
+ public static $httpStatuses = [
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 118 => 'Connection timed out',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 208 => 'Already Reported',
+ 210 => 'Content Different',
+ 226 => 'IM Used',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Reserved',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ 310 => 'Too many Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range unsatisfiable',
+ 417 => 'Expectation failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable entity',
+ 423 => 'Locked',
+ 424 => 'Method failure',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 449 => 'Retry With',
+ 450 => 'Blocked by Windows Parental Controls',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway ou Proxy Error',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 507 => 'Insufficient storage',
+ 508 => 'Loop Detected',
+ 509 => 'Bandwidth Limit Exceeded',
+ 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /**
+ * @var integer the HTTP status code to send with the response.
+ */
+ private $_statusCode = 200;
+ /**
+ * @var HeaderCollection
+ */
+ private $_headers;
+
+ /**
+ * Initializes this component.
+ */
+ public function init()
+ {
+ if ($this->version === null) {
+ if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === '1.0') {
+ $this->version = '1.0';
+ } else {
+ $this->version = '1.1';
+ }
+ }
+ if ($this->charset === null) {
+ $this->charset = Yii::$app->charset;
+ }
+ }
+
+ /**
+ * @return integer the HTTP status code to send with the response.
+ */
+ public function getStatusCode()
+ {
+ return $this->_statusCode;
+ }
+
+ /**
+ * Sets the response status code.
+ * This method will set the corresponding status text if `$text` is null.
+ * @param integer $value the status code
+ * @param string $text the status text. If not set, it will be set automatically based on the status code.
+ * @throws InvalidParamException if the status code is invalid.
+ */
+ public function setStatusCode($value, $text = null)
+ {
+ if ($value === null) {
+ $value = 200;
+ }
+ $this->_statusCode = (int) $value;
+ if ($this->getIsInvalid()) {
+ throw new InvalidParamException("The HTTP status code is invalid: $value");
+ }
+ if ($text === null) {
+ $this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : '';
+ } else {
+ $this->statusText = $text;
+ }
+ }
+
+ /**
+ * Returns the header collection.
+ * The header collection contains the currently registered HTTP headers.
+ * @return HeaderCollection the header collection
+ */
+ public function getHeaders()
+ {
+ if ($this->_headers === null) {
+ $this->_headers = new HeaderCollection;
+ }
+
+ return $this->_headers;
+ }
+
+ /**
+ * Sends the response to the client.
+ */
+ public function send()
+ {
+ if ($this->isSent) {
+ return;
+ }
+ $this->trigger(self::EVENT_BEFORE_SEND);
+ $this->prepare();
+ $this->trigger(self::EVENT_AFTER_PREPARE);
+ $this->sendHeaders();
+ $this->sendContent();
+ $this->trigger(self::EVENT_AFTER_SEND);
+ $this->isSent = true;
+ }
+
+ /**
+ * Clears the headers, cookies, content, status code of the response.
+ */
+ public function clear()
+ {
+ $this->_headers = null;
+ $this->_cookies = null;
+ $this->_statusCode = 200;
+ $this->statusText = 'OK';
+ $this->data = null;
+ $this->stream = null;
+ $this->content = null;
+ $this->isSent = false;
+ }
+
+ /**
+ * Sends the response headers to the client
+ */
+ protected function sendHeaders()
+ {
+ if (headers_sent()) {
+ return;
+ }
+ $statusCode = $this->getStatusCode();
+ header("HTTP/{$this->version} $statusCode {$this->statusText}");
+ if ($this->_headers) {
+ $headers = $this->getHeaders();
+ foreach ($headers as $name => $values) {
+ $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
+ foreach ($values as $value) {
+ header("$name: $value", false);
+ }
+ }
+ }
+ $this->sendCookies();
+ }
+
+ /**
+ * Sends the cookies to the client.
+ */
+ protected function sendCookies()
+ {
+ if ($this->_cookies === null) {
+ return;
+ }
+ $request = Yii::$app->getRequest();
+ if ($request->enableCookieValidation) {
+ $validationKey = $request->getCookieValidationKey();
+ }
+ foreach ($this->getCookies() as $cookie) {
+ $value = $cookie->value;
+ if ($cookie->expire != 1 && isset($validationKey)) {
+ $value = Security::hashData(serialize($value), $validationKey);
+ }
+ setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
+ }
+ $this->getCookies()->removeAll();
+ }
+
+ /**
+ * Sends the response content to the client
+ */
+ protected function sendContent()
+ {
+ if ($this->stream === null) {
+ echo $this->content;
+
+ return;
+ }
+
+ set_time_limit(0); // Reset time limit for big files
+ $chunkSize = 8 * 1024 * 1024; // 8MB per chunk
+
+ if (is_array($this->stream)) {
+ list ($handle, $begin, $end) = $this->stream;
+ fseek($handle, $begin);
+ while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
+ if ($pos + $chunkSize > $end) {
+ $chunkSize = $end - $pos + 1;
+ }
+ echo fread($handle, $chunkSize);
+ flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
+ }
+ fclose($handle);
+ } else {
+ while (!feof($this->stream)) {
+ echo fread($this->stream, $chunkSize);
+ flush();
+ }
+ fclose($this->stream);
+ }
+ }
+
+ /**
+ * Sends a file to the browser.
+ *
+ * Note that this method only prepares the response for file sending. The file is not sent
+ * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
+ *
+ * @param string $filePath the path of the file to be sent.
+ * @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`.
+ * @param string $mimeType the MIME type of the content. If null, it will be guessed based on `$filePath`
+ * @return static the response object itself
+ */
+ public function sendFile($filePath, $attachmentName = null, $mimeType = null)
+ {
+ if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
+ $mimeType = 'application/octet-stream';
+ }
+ if ($attachmentName === null) {
+ $attachmentName = basename($filePath);
+ }
+ $handle = fopen($filePath, 'rb');
+ $this->sendStreamAsFile($handle, $attachmentName, $mimeType);
+
+ return $this;
+ }
+
+ /**
+ * Sends the specified content as a file to the browser.
+ *
+ * Note that this method only prepares the response for file sending. The file is not sent
+ * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
+ *
+ * @param string $content the content to be sent. The existing [[content]] will be discarded.
+ * @param string $attachmentName the file name shown to the user.
+ * @param string $mimeType the MIME type of the content.
+ * @return static the response object itself
+ * @throws HttpException if the requested range is not satisfiable
+ */
+ public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream')
+ {
+ $headers = $this->getHeaders();
+ $contentLength = StringHelper::byteLength($content);
+ $range = $this->getHttpRange($contentLength);
+ if ($range === false) {
+ $headers->set('Content-Range', "bytes */$contentLength");
+ throw new HttpException(416, 'Requested range not satisfiable');
+ }
+
+ $headers->setDefault('Pragma', 'public')
+ ->setDefault('Accept-Ranges', 'bytes')
+ ->setDefault('Expires', '0')
+ ->setDefault('Content-Type', $mimeType)
+ ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+ ->setDefault('Content-Transfer-Encoding', 'binary')
+ ->setDefault('Content-Length', StringHelper::byteLength($content))
+ ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
+
+ list($begin, $end) = $range;
+ if ($begin !=0 || $end != $contentLength - 1) {
+ $this->setStatusCode(206);
+ $headers->set('Content-Range', "bytes $begin-$end/$contentLength");
+ $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1);
+ } else {
+ $this->setStatusCode(200);
+ $this->content = $content;
+ }
+
+ $this->format = self::FORMAT_RAW;
+
+ return $this;
+ }
+
+ /**
+ * Sends the specified stream as a file to the browser.
+ *
+ * Note that this method only prepares the response for file sending. The file is not sent
+ * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
+ *
+ * @param resource $handle the handle of the stream to be sent.
+ * @param string $attachmentName the file name shown to the user.
+ * @param string $mimeType the MIME type of the stream content.
+ * @return static the response object itself
+ * @throws HttpException if the requested range cannot be satisfied.
+ */
+ public function sendStreamAsFile($handle, $attachmentName, $mimeType = 'application/octet-stream')
+ {
+ $headers = $this->getHeaders();
+ fseek($handle, 0, SEEK_END);
+ $fileSize = ftell($handle);
+
+ $range = $this->getHttpRange($fileSize);
+ if ($range === false) {
+ $headers->set('Content-Range', "bytes */$fileSize");
+ throw new HttpException(416, 'Requested range not satisfiable');
+ }
+
+ list($begin, $end) = $range;
+ if ($begin !=0 || $end != $fileSize - 1) {
+ $this->setStatusCode(206);
+ $headers->set('Content-Range', "bytes $begin-$end/$fileSize");
+ } else {
+ $this->setStatusCode(200);
+ }
+
+ $length = $end - $begin + 1;
+
+ $headers->setDefault('Pragma', 'public')
+ ->setDefault('Accept-Ranges', 'bytes')
+ ->setDefault('Expires', '0')
+ ->setDefault('Content-Type', $mimeType)
+ ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+ ->setDefault('Content-Transfer-Encoding', 'binary')
+ ->setDefault('Content-Length', $length)
+ ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
+ $this->format = self::FORMAT_RAW;
+ $this->stream = [$handle, $begin, $end];
+
+ return $this;
+ }
+
+ /**
+ * Determines the HTTP range given in the request.
+ * @param integer $fileSize the size of the file that will be used to validate the requested HTTP range.
+ * @return array|boolean the range (begin, end), or false if the range request is invalid.
+ */
+ protected function getHttpRange($fileSize)
+ {
+ if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') {
+ return [0, $fileSize - 1];
+ }
+ if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) {
+ return false;
+ }
+ if ($matches[1] === '') {
+ $start = $fileSize - $matches[2];
+ $end = $fileSize - 1;
+ } elseif ($matches[2] !== '') {
+ $start = $matches[1];
+ $end = $matches[2];
+ if ($end >= $fileSize) {
+ $end = $fileSize - 1;
+ }
+ } else {
+ $start = $matches[1];
+ $end = $fileSize - 1;
+ }
+ if ($start < 0 || $start > $end) {
+ return false;
+ } else {
+ return [$start, $end];
+ }
+ }
+
+ /**
+ * Sends existing file to a browser as a download using x-sendfile.
+ *
+ * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver
+ * that in turn processes the request, this way eliminating the need to perform tasks like reading the file
+ * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great
+ * increase in performance as the web application is allowed to terminate earlier while the webserver is
+ * handling the request.
+ *
+ * The request is sent to the server through a special non-standard HTTP-header.
+ * When the web server encounters the presence of such header it will discard all output and send the file
+ * specified by that header using web server internals including all optimizations like caching-headers.
+ *
+ * As this header directive is non-standard different directives exists for different web servers applications:
+ *
+ * - Apache: [X-Sendfile](http://tn123.org/mod_xsendfile)
+ * - Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
+ * - Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
+ * - Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile)
+ * - Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile)
+ *
+ * So for this method to work the X-SENDFILE option/module should be enabled by the web server and
+ * a proper xHeader should be sent.
+ *
+ * **Note**
+ *
+ * This option allows to download files that are not under web folders, and even files that are otherwise protected
+ * (deny from all) like `.htaccess`.
+ *
+ * **Side effects**
+ *
+ * If this option is disabled by the web server, when this method is called a download configuration dialog
+ * will open but the downloaded file will have 0 bytes.
+ *
+ * **Known issues**
+ *
+ * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
+ * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site
+ * is either unavailable or cannot be found.". You can work around this problem by removing the `Pragma`-header.
+ *
+ * **Example**
+ *
+ * ~~~
+ * Yii::$app->response->xSendFile('/home/user/Pictures/picture1.jpg');
+ * ~~~
+ *
+ * @param string $filePath file name with full path
+ * @param string $mimeType the MIME type of the file. If null, it will be determined based on `$filePath`.
+ * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`.
+ * @param string $xHeader the name of the x-sendfile header.
+ * @return static the response object itself
+ */
+ public function xSendFile($filePath, $attachmentName = null, $mimeType = null, $xHeader = 'X-Sendfile')
+ {
+ if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
+ $mimeType = 'application/octet-stream';
+ }
+ if ($attachmentName === null) {
+ $attachmentName = basename($filePath);
+ }
+
+ $this->getHeaders()
+ ->setDefault($xHeader, $filePath)
+ ->setDefault('Content-Type', $mimeType)
+ ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
+
+ return $this;
+ }
+
+ /**
+ * Redirects the browser to the specified URL.
+ *
+ * This method adds a "Location" header to the current response. Note that it does not send out
+ * the header until [[send()]] is called. In a controller action you may use this method as follows:
+ *
+ * ~~~
+ * return Yii::$app->getResponse()->redirect($url);
+ * ~~~
+ *
+ * In other places, if you want to send out the "Location" header immediately, you should use
+ * the following code:
+ *
+ * ~~~
+ * Yii::$app->getResponse()->redirect($url)->send();
+ * return;
+ * ~~~
+ *
+ * In AJAX mode, this normally will not work as expected unless there are some
+ * client-side JavaScript code handling the redirection. To help achieve this goal,
+ * this method will send out a "X-Redirect" header instead of "Location".
+ *
+ * If you use the "yii" JavaScript module, it will handle the AJAX redirection as
+ * described above. Otherwise, you should write the following JavaScript code to
+ * handle the redirection:
+ *
+ * ~~~
+ * $document.ajaxComplete(function (event, xhr, settings) {
+ * var url = xhr.getResponseHeader('X-Redirect');
+ * if (url) {
+ * window.location = url;
+ * }
+ * });
+ * ~~~
+ *
+ * @param string|array $url the URL to be redirected to. This can be in one of the following formats:
+ *
+ * - a string representing a URL (e.g. "http://example.com")
+ * - a string representing a URL alias (e.g. "@example.com")
+ * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`).
+ * Note that the route is with respect to the whole application, instead of relative to a controller or module.
+ * [[Url::to()]] will be used to convert the array into a URL.
+ *
+ * Any relative URL will be converted into an absolute one by prepending it with the host info
+ * of the current request.
+ *
+ * @param integer $statusCode the HTTP status code. Defaults to 302.
+ * See
+ * for details about HTTP status code
+ * @return static the response object itself
+ */
+ public function redirect($url, $statusCode = 302)
+ {
+ if (is_array($url) && isset($url[0])) {
+ // ensure the route is absolute
+ $url[0] = '/' . ltrim($url[0], '/');
+ }
+ $url = Url::to($url);
+ if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
+ $url = Yii::$app->getRequest()->getHostInfo() . $url;
+ }
+
+ if (Yii::$app->getRequest()->getIsPjax()) {
+ $this->getHeaders()->set('X-Pjax-Url', $url);
+ } elseif (Yii::$app->getRequest()->getIsAjax()) {
+ $this->getHeaders()->set('X-Redirect', $url);
+ } else {
+ $this->getHeaders()->set('Location', $url);
+ }
+ $this->setStatusCode($statusCode);
+
+ return $this;
+ }
+
+ /**
+ * Refreshes the current page.
+ * The effect of this method call is the same as the user pressing the refresh button of his browser
+ * (without re-posting data).
+ *
+ * In a controller action you may use this method like this:
+ *
+ * ~~~
+ * return Yii::$app->getResponse()->refresh();
+ * ~~~
+ *
+ * @param string $anchor the anchor that should be appended to the redirection URL.
+ * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
+ * @return Response the response object itself
+ */
+ public function refresh($anchor = '')
+ {
+ return $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
+ }
+
+ private $_cookies;
+
+ /**
+ * Returns the cookie collection.
+ * Through the returned cookie collection, you add or remove cookies as follows,
+ *
+ * ~~~
+ * // add a cookie
+ * $response->cookies->add(new Cookie([
+ * 'name' => $name,
+ * 'value' => $value,
+ * ]);
+ *
+ * // remove a cookie
+ * $response->cookies->remove('name');
+ * // alternatively
+ * unset($response->cookies['name']);
+ * ~~~
+ *
+ * @return CookieCollection the cookie collection.
+ */
+ public function getCookies()
+ {
+ if ($this->_cookies === null) {
+ $this->_cookies = new CookieCollection;
+ }
+
+ return $this->_cookies;
+ }
+
+ /**
+ * @return boolean whether this response has a valid [[statusCode]].
+ */
+ public function getIsInvalid()
+ {
+ return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
+ }
+
+ /**
+ * @return boolean whether this response is informational
+ */
+ public function getIsInformational()
+ {
+ return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
+ }
+
+ /**
+ * @return boolean whether this response is successful
+ */
+ public function getIsSuccessful()
+ {
+ return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
+ }
+
+ /**
+ * @return boolean whether this response is a redirection
+ */
+ public function getIsRedirection()
+ {
+ return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
+ }
+
+ /**
+ * @return boolean whether this response indicates a client error
+ */
+ public function getIsClientError()
+ {
+ return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
+ }
+
+ /**
+ * @return boolean whether this response indicates a server error
+ */
+ public function getIsServerError()
+ {
+ return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
+ }
+
+ /**
+ * @return boolean whether this response is OK
+ */
+ public function getIsOk()
+ {
+ return $this->getStatusCode() == 200;
+ }
+
+ /**
+ * @return boolean whether this response indicates the current request is forbidden
+ */
+ public function getIsForbidden()
+ {
+ return $this->getStatusCode() == 403;
+ }
+
+ /**
+ * @return boolean whether this response indicates the currently requested resource is not found
+ */
+ public function getIsNotFound()
+ {
+ return $this->getStatusCode() == 404;
+ }
+
+ /**
+ * @return boolean whether this response is empty
+ */
+ public function getIsEmpty()
+ {
+ return in_array($this->getStatusCode(), [201, 204, 304]);
+ }
+
+ /**
+ * Prepares for sending the response.
+ * The default implementation will convert [[data]] into [[content]] and set headers accordingly.
+ * @throws InvalidConfigException if the formatter for the specified format is invalid or [[format]] is not supported
+ */
+ protected function prepare()
+ {
+ if ($this->stream !== null || $this->data === null) {
+ return;
+ }
+
+ if (isset($this->formatters[$this->format])) {
+ $formatter = $this->formatters[$this->format];
+ if (!is_object($formatter)) {
+ $formatter = Yii::createObject($formatter);
+ }
+ if ($formatter instanceof ResponseFormatterInterface) {
+ $formatter->format($this);
+ } else {
+ throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
+ }
+ } else {
+ switch ($this->format) {
+ case self::FORMAT_HTML:
+ $this->getHeaders()->setDefault('Content-Type', 'text/html; charset=' . $this->charset);
+ $this->content = $this->data;
+ break;
+ case self::FORMAT_RAW:
+ $this->content = $this->data;
+ break;
+ case self::FORMAT_JSON:
+ $this->getHeaders()->set('Content-Type', 'application/json; charset=UTF-8');
+ $this->content = Json::encode($this->data);
+ break;
+ case self::FORMAT_JSONP:
+ $this->getHeaders()->set('Content-Type', 'text/javascript; charset=' . $this->charset);
+ if (is_array($this->data) && isset($this->data['data'], $this->data['callback'])) {
+ $this->content = sprintf('%s(%s);', $this->data['callback'], Json::encode($this->data['data']));
+ } else {
+ $this->content = '';
+ Yii::warning("The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.", __METHOD__);
+ }
+ break;
+ case self::FORMAT_XML:
+ Yii::createObject(XmlResponseFormatter::className())->format($this);
+ break;
+ default:
+ throw new InvalidConfigException("Unsupported response format: {$this->format}");
+ }
+ }
+
+ if (is_array($this->content)) {
+ throw new InvalidParamException("Response content must not be an array.");
+ } elseif (is_object($this->content)) {
+ if (method_exists($this->content, '__toString')) {
+ $this->content = $this->content->__toString();
+ } else {
+ throw new InvalidParamException("Response content must be a string or an object implementing __toString().");
+ }
+ }
+ }
}
diff --git a/framework/web/ResponseFormatterInterface.php b/framework/web/ResponseFormatterInterface.php
index 689ee1e790d..585c755a8dd 100644
--- a/framework/web/ResponseFormatterInterface.php
+++ b/framework/web/ResponseFormatterInterface.php
@@ -15,9 +15,9 @@
*/
interface ResponseFormatterInterface
{
- /**
- * Formats the specified response.
- * @param Response $response the response to be formatted.
- */
- public function format($response);
+ /**
+ * Formats the specified response.
+ * @param Response $response the response to be formatted.
+ */
+ public function format($response);
}
diff --git a/framework/web/Session.php b/framework/web/Session.php
index d08847f4c13..fac6bdb4929 100644
--- a/framework/web/Session.php
+++ b/framework/web/Session.php
@@ -74,690 +74,700 @@
*/
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
{
- /**
- * @var string the name of the session variable that stores the flash message data.
- */
- public $flashParam = '__flash';
- /**
- * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
- */
- public $handler;
- /**
- * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
- * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httpOnly'
- * @see http://www.php.net/manual/en/function.session-set-cookie-params.php
- */
- private $_cookieParams = ['httpOnly' => true];
-
- /**
- * Initializes the application component.
- * This method is required by IApplicationComponent and is invoked by application.
- */
- public function init()
- {
- parent::init();
- register_shutdown_function([$this, 'close']);
- }
-
- /**
- * Returns a value indicating whether to use custom session storage.
- * This method should be overridden to return true by child classes that implement custom session storage.
- * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
- * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
- * @return boolean whether to use custom storage.
- */
- public function getUseCustomStorage()
- {
- return false;
- }
-
- /**
- * Starts the session.
- */
- public function open()
- {
- if ($this->getIsActive()) {
- return;
- }
-
- $this->registerSessionHandler();
-
- $this->setCookieParamsInternal();
-
- @session_start();
-
- if ($this->getIsActive()) {
- Yii::info('Session started', __METHOD__);
- $this->updateFlashCounters();
- } else {
- $error = error_get_last();
- $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
- Yii::error($message, __METHOD__);
- }
- }
-
- /**
- * Registers session handler.
- * @throws \yii\base\InvalidConfigException
- */
- protected function registerSessionHandler()
- {
- if ($this->handler !== null) {
- if (!is_object($this->handler)) {
- $this->handler = Yii::createObject($this->handler);
- }
- if (!$this->handler instanceof \SessionHandlerInterface) {
- throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
- }
- @session_set_save_handler($this->handler, false);
- } elseif ($this->getUseCustomStorage()) {
- @session_set_save_handler(
- [$this, 'openSession'],
- [$this, 'closeSession'],
- [$this, 'readSession'],
- [$this, 'writeSession'],
- [$this, 'destroySession'],
- [$this, 'gcSession']
- );
- }
- }
-
- /**
- * Ends the current session and store session data.
- */
- public function close()
- {
- if ($this->getIsActive()) {
- @session_write_close();
- }
- }
-
- /**
- * Frees all session variables and destroys all data registered to a session.
- */
- public function destroy()
- {
- if ($this->getIsActive()) {
- @session_unset();
- @session_destroy();
- }
- }
-
- /**
- * @return boolean whether the session has started
- */
- public function getIsActive()
- {
- return session_status() == PHP_SESSION_ACTIVE;
- }
-
- private $_hasSessionId;
-
- /**
- * Returns a value indicating whether the current request has sent the session ID.
- * The default implementation will check cookie and $_GET using the session name.
- * If you send session ID via other ways, you may need to override this method
- * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
- * @return boolean whether the current request has sent the session ID.
- */
- public function getHasSessionId()
- {
- if ($this->_hasSessionId === null) {
- $name = $this->getName();
- $request = Yii::$app->getRequest();
- if (ini_get('session.use_cookies') && !empty($_COOKIE[$name])) {
- $this->_hasSessionId = true;
- } elseif (!ini_get('use_only_cookies') && ini_get('use_trans_sid')) {
- $this->_hasSessionId = $request->get($name) !== null;
- } else {
- $this->_hasSessionId = false;
- }
- }
- return $this->_hasSessionId;
- }
-
- /**
- * Sets the value indicating whether the current request has sent the session ID.
- * This method is provided so that you can override the default way of determining
- * whether the session ID is sent.
- * @param boolean $value whether the current request has sent the session ID.
- */
- public function setHasSessionId($value)
- {
- $this->_hasSessionId = $value;
- }
-
- /**
- * @return string the current session ID
- */
- public function getId()
- {
- return session_id();
- }
-
- /**
- * @param string $value the session ID for the current session
- */
- public function setId($value)
- {
- session_id($value);
- }
-
- /**
- * Updates the current session ID with a newly generated one .
- * Please refer to for more details.
- * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
- */
- public function regenerateID($deleteOldSession = false)
- {
- // add @ to inhibit possible warning due to race condition
- // https://github.com/yiisoft/yii2/pull/1812
- @session_regenerate_id($deleteOldSession);
- }
-
- /**
- * @return string the current session name
- */
- public function getName()
- {
- return session_name();
- }
-
- /**
- * @param string $value the session name for the current session, must be an alphanumeric string.
- * It defaults to "PHPSESSID".
- */
- public function setName($value)
- {
- session_name($value);
- }
-
- /**
- * @return string the current session save path, defaults to '/tmp'.
- */
- public function getSavePath()
- {
- return session_save_path();
- }
-
- /**
- * @param string $value the current session save path. This can be either a directory name or a path alias.
- * @throws InvalidParamException if the path is not a valid directory
- */
- public function setSavePath($value)
- {
- $path = Yii::getAlias($value);
- if (is_dir($path)) {
- session_save_path($path);
- } else {
- throw new InvalidParamException("Session save path is not a valid directory: $value");
- }
- }
-
- /**
- * @return array the session cookie parameters.
- * @see http://us2.php.net/manual/en/function.session-get-cookie-params.php
- */
- public function getCookieParams()
- {
- $params = session_get_cookie_params();
- if (isset($params['httponly'])) {
- $params['httpOnly'] = $params['httponly'];
- unset($params['httponly']);
- }
- return array_merge($params, $this->_cookieParams);
- }
-
- /**
- * Sets the session cookie parameters.
- * The cookie parameters passed to this method will be merged with the result
- * of `session_get_cookie_params()`.
- * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`.
- * @throws InvalidParamException if the parameters are incomplete.
- * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
- */
- public function setCookieParams(array $value)
- {
- $this->_cookieParams = $value;
- }
-
- /**
- * Sets the session cookie parameters.
- * This method is called by [[open()]] when it is about to open the session.
- * @throws InvalidParamException if the parameters are incomplete.
- * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
- */
- private function setCookieParamsInternal()
- {
- $data = $this->getCookieParams();
- extract($data);
- if (isset($lifetime, $path, $domain, $secure, $httpOnly)) {
- session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
- } else {
- throw new InvalidParamException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httpOnly.');
- }
- }
-
- /**
- * Returns the value indicating whether cookies should be used to store session IDs.
- * @return boolean|null the value indicating whether cookies should be used to store session IDs.
- * @see setUseCookies()
- */
- public function getUseCookies()
- {
- if (ini_get('session.use_cookies') === '0') {
- return false;
- } elseif (ini_get('session.use_only_cookies') === '1') {
- return true;
- } else {
- return null;
- }
- }
-
- /**
- * Sets the value indicating whether cookies should be used to store session IDs.
- * Three states are possible:
- *
- * - true: cookies and only cookies will be used to store session IDs.
- * - false: cookies will not be used to store session IDs.
- * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
- *
- * @param boolean|null $value the value indicating whether cookies should be used to store session IDs.
- */
- public function setUseCookies($value)
- {
- if ($value === false) {
- ini_set('session.use_cookies', '0');
- ini_set('session.use_only_cookies', '0');
- } elseif ($value === true) {
- ini_set('session.use_cookies', '1');
- ini_set('session.use_only_cookies', '1');
- } else {
- ini_set('session.use_cookies', '1');
- ini_set('session.use_only_cookies', '0');
- }
- }
-
- /**
- * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
- */
- public function getGCProbability()
- {
- return (float)(ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
- }
-
- /**
- * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
- * @throws InvalidParamException if the value is not between 0 and 100.
- */
- public function setGCProbability($value)
- {
- if ($value >= 0 && $value <= 100) {
- // percent * 21474837 / 2147483647 ≈ percent * 0.01
- ini_set('session.gc_probability', floor($value * 21474836.47));
- ini_set('session.gc_divisor', 2147483647);
- } else {
- throw new InvalidParamException('GCProbability must be a value between 0 and 100.');
- }
- }
-
- /**
- * @return boolean whether transparent sid support is enabled or not, defaults to false.
- */
- public function getUseTransparentSessionID()
- {
- return ini_get('session.use_trans_sid') == 1;
- }
-
- /**
- * @param boolean $value whether transparent sid support is enabled or not.
- */
- public function setUseTransparentSessionID($value)
- {
- ini_set('session.use_trans_sid', $value ? '1' : '0');
- }
-
- /**
- * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up.
- * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
- */
- public function getTimeout()
- {
- return (int)ini_get('session.gc_maxlifetime');
- }
-
- /**
- * @param integer $value the number of seconds after which data will be seen as 'garbage' and cleaned up
- */
- public function setTimeout($value)
- {
- ini_set('session.gc_maxlifetime', $value);
- }
-
- /**
- * Session open handler.
- * This method should be overridden if [[useCustomStorage()]] returns true.
- * Do not call this method directly.
- * @param string $savePath session save path
- * @param string $sessionName session name
- * @return boolean whether session is opened successfully
- */
- public function openSession($savePath, $sessionName)
- {
- return true;
- }
-
- /**
- * Session close handler.
- * This method should be overridden if [[useCustomStorage()]] returns true.
- * Do not call this method directly.
- * @return boolean whether session is closed successfully
- */
- public function closeSession()
- {
- return true;
- }
-
- /**
- * Session read handler.
- * This method should be overridden if [[useCustomStorage()]] returns true.
- * Do not call this method directly.
- * @param string $id session ID
- * @return string the session data
- */
- public function readSession($id)
- {
- return '';
- }
-
- /**
- * Session write handler.
- * This method should be overridden if [[useCustomStorage()]] returns true.
- * Do not call this method directly.
- * @param string $id session ID
- * @param string $data session data
- * @return boolean whether session write is successful
- */
- public function writeSession($id, $data)
- {
- return true;
- }
-
- /**
- * Session destroy handler.
- * This method should be overridden if [[useCustomStorage()]] returns true.
- * Do not call this method directly.
- * @param string $id session ID
- * @return boolean whether session is destroyed successfully
- */
- public function destroySession($id)
- {
- return true;
- }
-
- /**
- * Session GC (garbage collection) handler.
- * This method should be overridden if [[useCustomStorage()]] returns true.
- * Do not call this method directly.
- * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
- * @return boolean whether session is GCed successfully
- */
- public function gcSession($maxLifetime)
- {
- return true;
- }
-
- /**
- * Returns an iterator for traversing the session variables.
- * This method is required by the interface IteratorAggregate.
- * @return SessionIterator an iterator for traversing the session variables.
- */
- public function getIterator()
- {
- return new SessionIterator;
- }
-
- /**
- * Returns the number of items in the session.
- * @return integer the number of session variables
- */
- public function getCount()
- {
- return count($_SESSION);
- }
-
- /**
- * Returns the number of items in the session.
- * This method is required by Countable interface.
- * @return integer number of items in the session.
- */
- public function count()
- {
- return $this->getCount();
- }
-
- /**
- * Returns the session variable value with the session variable name.
- * If the session variable does not exist, the `$defaultValue` will be returned.
- * @param string $key the session variable name
- * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
- * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
- */
- public function get($key, $defaultValue = null)
- {
- $this->open();
- return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
- }
-
- /**
- * Adds a session variable.
- * If the specified name already exists, the old value will be overwritten.
- * @param string $key session variable name
- * @param mixed $value session variable value
- */
- public function set($key, $value)
- {
- $this->open();
- $_SESSION[$key] = $value;
- }
-
- /**
- * Removes a session variable.
- * @param string $key the name of the session variable to be removed
- * @return mixed the removed value, null if no such session variable.
- */
- public function remove($key)
- {
- $this->open();
- if (isset($_SESSION[$key])) {
- $value = $_SESSION[$key];
- unset($_SESSION[$key]);
- return $value;
- } else {
- return null;
- }
- }
-
- /**
- * Removes all session variables
- */
- public function removeAll()
- {
- $this->open();
- foreach (array_keys($_SESSION) as $key) {
- unset($_SESSION[$key]);
- }
- }
-
- /**
- * @param mixed $key session variable name
- * @return boolean whether there is the named session variable
- */
- public function has($key)
- {
- $this->open();
- return isset($_SESSION[$key]);
- }
-
- /**
- * Updates the counters for flash messages and removes outdated flash messages.
- * This method should only be called once in [[init()]].
- */
- protected function updateFlashCounters()
- {
- $counters = $this->get($this->flashParam, []);
- if (is_array($counters)) {
- foreach ($counters as $key => $count) {
- if ($count) {
- unset($counters[$key], $_SESSION[$key]);
- } else {
- $counters[$key]++;
- }
- }
- $_SESSION[$this->flashParam] = $counters;
- } else {
- // fix the unexpected problem that flashParam doesn't return an array
- unset($_SESSION[$this->flashParam]);
- }
- }
-
- /**
- * Returns a flash message.
- * A flash message is available only in the current request and the next request.
- * @param string $key the key identifying the flash message
- * @param mixed $defaultValue value to be returned if the flash message does not exist.
- * @param boolean $delete whether to delete this flash message right after this method is called.
- * If false, the flash message will be automatically deleted after the next request.
- * @return mixed the flash message
- */
- public function getFlash($key, $defaultValue = null, $delete = false)
- {
- $counters = $this->get($this->flashParam, []);
- if (isset($counters[$key])) {
- $value = $this->get($key, $defaultValue);
- if ($delete) {
- $this->removeFlash($key);
- }
- return $value;
- } else {
- return $defaultValue;
- }
- }
-
- /**
- * Returns all flash messages.
- * @return array flash messages (key => message).
- */
- public function getAllFlashes()
- {
- $counters = $this->get($this->flashParam, []);
- $flashes = [];
- foreach (array_keys($counters) as $key) {
- if (isset($_SESSION[$key])) {
- $flashes[$key] = $_SESSION[$key];
- }
- }
- return $flashes;
- }
-
- /**
- * Stores a flash message.
- * A flash message is available only in the current request and the next request.
- * @param string $key the key identifying the flash message. Note that flash messages
- * and normal session variables share the same name space. If you have a normal
- * session variable using the same name, its value will be overwritten by this method.
- * @param mixed $value flash message
- */
- public function setFlash($key, $value = true)
- {
- $counters = $this->get($this->flashParam, []);
- $counters[$key] = 0;
- $_SESSION[$key] = $value;
- $_SESSION[$this->flashParam] = $counters;
- }
-
- /**
- * Removes a flash message.
- * Note that flash messages will be automatically removed after the next request.
- * @param string $key the key identifying the flash message. Note that flash messages
- * and normal session variables share the same name space. If you have a normal
- * session variable using the same name, it will be removed by this method.
- * @return mixed the removed flash message. Null if the flash message does not exist.
- */
- public function removeFlash($key)
- {
- $counters = $this->get($this->flashParam, []);
- $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
- unset($counters[$key], $_SESSION[$key]);
- $_SESSION[$this->flashParam] = $counters;
- return $value;
- }
-
- /**
- * Removes all flash messages.
- * Note that flash messages and normal session variables share the same name space.
- * If you have a normal session variable using the same name, it will be removed
- * by this method.
- */
- public function removeAllFlashes()
- {
- $counters = $this->get($this->flashParam, []);
- foreach (array_keys($counters) as $key) {
- unset($_SESSION[$key]);
- }
- unset($_SESSION[$this->flashParam]);
- }
-
- /**
- * Returns a value indicating whether there is a flash message associated with the specified key.
- * @param string $key key identifying the flash message
- * @return boolean whether the specified flash message exists
- */
- public function hasFlash($key)
- {
- return $this->getFlash($key) !== null;
- }
-
- /**
- * This method is required by the interface ArrayAccess.
- * @param mixed $offset the offset to check on
- * @return boolean
- */
- public function offsetExists($offset)
- {
- $this->open();
- return isset($_SESSION[$offset]);
- }
-
- /**
- * This method is required by the interface ArrayAccess.
- * @param integer $offset the offset to retrieve element.
- * @return mixed the element at the offset, null if no element is found at the offset
- */
- public function offsetGet($offset)
- {
- $this->open();
- return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
- }
-
- /**
- * This method is required by the interface ArrayAccess.
- * @param integer $offset the offset to set element
- * @param mixed $item the element value
- */
- public function offsetSet($offset, $item)
- {
- $this->open();
- $_SESSION[$offset] = $item;
- }
-
- /**
- * This method is required by the interface ArrayAccess.
- * @param mixed $offset the offset to unset element
- */
- public function offsetUnset($offset)
- {
- $this->open();
- unset($_SESSION[$offset]);
- }
+ /**
+ * @var string the name of the session variable that stores the flash message data.
+ */
+ public $flashParam = '__flash';
+ /**
+ * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
+ */
+ public $handler;
+ /**
+ * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
+ * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httpOnly'
+ * @see http://www.php.net/manual/en/function.session-set-cookie-params.php
+ */
+ private $_cookieParams = ['httpOnly' => true];
+
+ /**
+ * Initializes the application component.
+ * This method is required by IApplicationComponent and is invoked by application.
+ */
+ public function init()
+ {
+ parent::init();
+ register_shutdown_function([$this, 'close']);
+ }
+
+ /**
+ * Returns a value indicating whether to use custom session storage.
+ * This method should be overridden to return true by child classes that implement custom session storage.
+ * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
+ * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
+ * @return boolean whether to use custom storage.
+ */
+ public function getUseCustomStorage()
+ {
+ return false;
+ }
+
+ /**
+ * Starts the session.
+ */
+ public function open()
+ {
+ if ($this->getIsActive()) {
+ return;
+ }
+
+ $this->registerSessionHandler();
+
+ $this->setCookieParamsInternal();
+
+ @session_start();
+
+ if ($this->getIsActive()) {
+ Yii::info('Session started', __METHOD__);
+ $this->updateFlashCounters();
+ } else {
+ $error = error_get_last();
+ $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
+ Yii::error($message, __METHOD__);
+ }
+ }
+
+ /**
+ * Registers session handler.
+ * @throws \yii\base\InvalidConfigException
+ */
+ protected function registerSessionHandler()
+ {
+ if ($this->handler !== null) {
+ if (!is_object($this->handler)) {
+ $this->handler = Yii::createObject($this->handler);
+ }
+ if (!$this->handler instanceof \SessionHandlerInterface) {
+ throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
+ }
+ @session_set_save_handler($this->handler, false);
+ } elseif ($this->getUseCustomStorage()) {
+ @session_set_save_handler(
+ [$this, 'openSession'],
+ [$this, 'closeSession'],
+ [$this, 'readSession'],
+ [$this, 'writeSession'],
+ [$this, 'destroySession'],
+ [$this, 'gcSession']
+ );
+ }
+ }
+
+ /**
+ * Ends the current session and store session data.
+ */
+ public function close()
+ {
+ if ($this->getIsActive()) {
+ @session_write_close();
+ }
+ }
+
+ /**
+ * Frees all session variables and destroys all data registered to a session.
+ */
+ public function destroy()
+ {
+ if ($this->getIsActive()) {
+ @session_unset();
+ @session_destroy();
+ }
+ }
+
+ /**
+ * @return boolean whether the session has started
+ */
+ public function getIsActive()
+ {
+ return session_status() == PHP_SESSION_ACTIVE;
+ }
+
+ private $_hasSessionId;
+
+ /**
+ * Returns a value indicating whether the current request has sent the session ID.
+ * The default implementation will check cookie and $_GET using the session name.
+ * If you send session ID via other ways, you may need to override this method
+ * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
+ * @return boolean whether the current request has sent the session ID.
+ */
+ public function getHasSessionId()
+ {
+ if ($this->_hasSessionId === null) {
+ $name = $this->getName();
+ $request = Yii::$app->getRequest();
+ if (ini_get('session.use_cookies') && !empty($_COOKIE[$name])) {
+ $this->_hasSessionId = true;
+ } elseif (!ini_get('use_only_cookies') && ini_get('use_trans_sid')) {
+ $this->_hasSessionId = $request->get($name) !== null;
+ } else {
+ $this->_hasSessionId = false;
+ }
+ }
+
+ return $this->_hasSessionId;
+ }
+
+ /**
+ * Sets the value indicating whether the current request has sent the session ID.
+ * This method is provided so that you can override the default way of determining
+ * whether the session ID is sent.
+ * @param boolean $value whether the current request has sent the session ID.
+ */
+ public function setHasSessionId($value)
+ {
+ $this->_hasSessionId = $value;
+ }
+
+ /**
+ * @return string the current session ID
+ */
+ public function getId()
+ {
+ return session_id();
+ }
+
+ /**
+ * @param string $value the session ID for the current session
+ */
+ public function setId($value)
+ {
+ session_id($value);
+ }
+
+ /**
+ * Updates the current session ID with a newly generated one .
+ * Please refer to for more details.
+ * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
+ */
+ public function regenerateID($deleteOldSession = false)
+ {
+ // add @ to inhibit possible warning due to race condition
+ // https://github.com/yiisoft/yii2/pull/1812
+ @session_regenerate_id($deleteOldSession);
+ }
+
+ /**
+ * @return string the current session name
+ */
+ public function getName()
+ {
+ return session_name();
+ }
+
+ /**
+ * @param string $value the session name for the current session, must be an alphanumeric string.
+ * It defaults to "PHPSESSID".
+ */
+ public function setName($value)
+ {
+ session_name($value);
+ }
+
+ /**
+ * @return string the current session save path, defaults to '/tmp'.
+ */
+ public function getSavePath()
+ {
+ return session_save_path();
+ }
+
+ /**
+ * @param string $value the current session save path. This can be either a directory name or a path alias.
+ * @throws InvalidParamException if the path is not a valid directory
+ */
+ public function setSavePath($value)
+ {
+ $path = Yii::getAlias($value);
+ if (is_dir($path)) {
+ session_save_path($path);
+ } else {
+ throw new InvalidParamException("Session save path is not a valid directory: $value");
+ }
+ }
+
+ /**
+ * @return array the session cookie parameters.
+ * @see http://us2.php.net/manual/en/function.session-get-cookie-params.php
+ */
+ public function getCookieParams()
+ {
+ $params = session_get_cookie_params();
+ if (isset($params['httponly'])) {
+ $params['httpOnly'] = $params['httponly'];
+ unset($params['httponly']);
+ }
+
+ return array_merge($params, $this->_cookieParams);
+ }
+
+ /**
+ * Sets the session cookie parameters.
+ * The cookie parameters passed to this method will be merged with the result
+ * of `session_get_cookie_params()`.
+ * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`.
+ * @throws InvalidParamException if the parameters are incomplete.
+ * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
+ */
+ public function setCookieParams(array $value)
+ {
+ $this->_cookieParams = $value;
+ }
+
+ /**
+ * Sets the session cookie parameters.
+ * This method is called by [[open()]] when it is about to open the session.
+ * @throws InvalidParamException if the parameters are incomplete.
+ * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
+ */
+ private function setCookieParamsInternal()
+ {
+ $data = $this->getCookieParams();
+ extract($data);
+ if (isset($lifetime, $path, $domain, $secure, $httpOnly)) {
+ session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
+ } else {
+ throw new InvalidParamException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httpOnly.');
+ }
+ }
+
+ /**
+ * Returns the value indicating whether cookies should be used to store session IDs.
+ * @return boolean|null the value indicating whether cookies should be used to store session IDs.
+ * @see setUseCookies()
+ */
+ public function getUseCookies()
+ {
+ if (ini_get('session.use_cookies') === '0') {
+ return false;
+ } elseif (ini_get('session.use_only_cookies') === '1') {
+ return true;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the value indicating whether cookies should be used to store session IDs.
+ * Three states are possible:
+ *
+ * - true: cookies and only cookies will be used to store session IDs.
+ * - false: cookies will not be used to store session IDs.
+ * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
+ *
+ * @param boolean|null $value the value indicating whether cookies should be used to store session IDs.
+ */
+ public function setUseCookies($value)
+ {
+ if ($value === false) {
+ ini_set('session.use_cookies', '0');
+ ini_set('session.use_only_cookies', '0');
+ } elseif ($value === true) {
+ ini_set('session.use_cookies', '1');
+ ini_set('session.use_only_cookies', '1');
+ } else {
+ ini_set('session.use_cookies', '1');
+ ini_set('session.use_only_cookies', '0');
+ }
+ }
+
+ /**
+ * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
+ */
+ public function getGCProbability()
+ {
+ return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
+ }
+
+ /**
+ * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
+ * @throws InvalidParamException if the value is not between 0 and 100.
+ */
+ public function setGCProbability($value)
+ {
+ if ($value >= 0 && $value <= 100) {
+ // percent * 21474837 / 2147483647 ≈ percent * 0.01
+ ini_set('session.gc_probability', floor($value * 21474836.47));
+ ini_set('session.gc_divisor', 2147483647);
+ } else {
+ throw new InvalidParamException('GCProbability must be a value between 0 and 100.');
+ }
+ }
+
+ /**
+ * @return boolean whether transparent sid support is enabled or not, defaults to false.
+ */
+ public function getUseTransparentSessionID()
+ {
+ return ini_get('session.use_trans_sid') == 1;
+ }
+
+ /**
+ * @param boolean $value whether transparent sid support is enabled or not.
+ */
+ public function setUseTransparentSessionID($value)
+ {
+ ini_set('session.use_trans_sid', $value ? '1' : '0');
+ }
+
+ /**
+ * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up.
+ * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
+ */
+ public function getTimeout()
+ {
+ return (int) ini_get('session.gc_maxlifetime');
+ }
+
+ /**
+ * @param integer $value the number of seconds after which data will be seen as 'garbage' and cleaned up
+ */
+ public function setTimeout($value)
+ {
+ ini_set('session.gc_maxlifetime', $value);
+ }
+
+ /**
+ * Session open handler.
+ * This method should be overridden if [[useCustomStorage()]] returns true.
+ * Do not call this method directly.
+ * @param string $savePath session save path
+ * @param string $sessionName session name
+ * @return boolean whether session is opened successfully
+ */
+ public function openSession($savePath, $sessionName)
+ {
+ return true;
+ }
+
+ /**
+ * Session close handler.
+ * This method should be overridden if [[useCustomStorage()]] returns true.
+ * Do not call this method directly.
+ * @return boolean whether session is closed successfully
+ */
+ public function closeSession()
+ {
+ return true;
+ }
+
+ /**
+ * Session read handler.
+ * This method should be overridden if [[useCustomStorage()]] returns true.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return string the session data
+ */
+ public function readSession($id)
+ {
+ return '';
+ }
+
+ /**
+ * Session write handler.
+ * This method should be overridden if [[useCustomStorage()]] returns true.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @param string $data session data
+ * @return boolean whether session write is successful
+ */
+ public function writeSession($id, $data)
+ {
+ return true;
+ }
+
+ /**
+ * Session destroy handler.
+ * This method should be overridden if [[useCustomStorage()]] returns true.
+ * Do not call this method directly.
+ * @param string $id session ID
+ * @return boolean whether session is destroyed successfully
+ */
+ public function destroySession($id)
+ {
+ return true;
+ }
+
+ /**
+ * Session GC (garbage collection) handler.
+ * This method should be overridden if [[useCustomStorage()]] returns true.
+ * Do not call this method directly.
+ * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
+ * @return boolean whether session is GCed successfully
+ */
+ public function gcSession($maxLifetime)
+ {
+ return true;
+ }
+
+ /**
+ * Returns an iterator for traversing the session variables.
+ * This method is required by the interface IteratorAggregate.
+ * @return SessionIterator an iterator for traversing the session variables.
+ */
+ public function getIterator()
+ {
+ return new SessionIterator;
+ }
+
+ /**
+ * Returns the number of items in the session.
+ * @return integer the number of session variables
+ */
+ public function getCount()
+ {
+ return count($_SESSION);
+ }
+
+ /**
+ * Returns the number of items in the session.
+ * This method is required by Countable interface.
+ * @return integer number of items in the session.
+ */
+ public function count()
+ {
+ return $this->getCount();
+ }
+
+ /**
+ * Returns the session variable value with the session variable name.
+ * If the session variable does not exist, the `$defaultValue` will be returned.
+ * @param string $key the session variable name
+ * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
+ * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
+ */
+ public function get($key, $defaultValue = null)
+ {
+ $this->open();
+
+ return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
+ }
+
+ /**
+ * Adds a session variable.
+ * If the specified name already exists, the old value will be overwritten.
+ * @param string $key session variable name
+ * @param mixed $value session variable value
+ */
+ public function set($key, $value)
+ {
+ $this->open();
+ $_SESSION[$key] = $value;
+ }
+
+ /**
+ * Removes a session variable.
+ * @param string $key the name of the session variable to be removed
+ * @return mixed the removed value, null if no such session variable.
+ */
+ public function remove($key)
+ {
+ $this->open();
+ if (isset($_SESSION[$key])) {
+ $value = $_SESSION[$key];
+ unset($_SESSION[$key]);
+
+ return $value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Removes all session variables
+ */
+ public function removeAll()
+ {
+ $this->open();
+ foreach (array_keys($_SESSION) as $key) {
+ unset($_SESSION[$key]);
+ }
+ }
+
+ /**
+ * @param mixed $key session variable name
+ * @return boolean whether there is the named session variable
+ */
+ public function has($key)
+ {
+ $this->open();
+
+ return isset($_SESSION[$key]);
+ }
+
+ /**
+ * Updates the counters for flash messages and removes outdated flash messages.
+ * This method should only be called once in [[init()]].
+ */
+ protected function updateFlashCounters()
+ {
+ $counters = $this->get($this->flashParam, []);
+ if (is_array($counters)) {
+ foreach ($counters as $key => $count) {
+ if ($count) {
+ unset($counters[$key], $_SESSION[$key]);
+ } else {
+ $counters[$key]++;
+ }
+ }
+ $_SESSION[$this->flashParam] = $counters;
+ } else {
+ // fix the unexpected problem that flashParam doesn't return an array
+ unset($_SESSION[$this->flashParam]);
+ }
+ }
+
+ /**
+ * Returns a flash message.
+ * A flash message is available only in the current request and the next request.
+ * @param string $key the key identifying the flash message
+ * @param mixed $defaultValue value to be returned if the flash message does not exist.
+ * @param boolean $delete whether to delete this flash message right after this method is called.
+ * If false, the flash message will be automatically deleted after the next request.
+ * @return mixed the flash message
+ */
+ public function getFlash($key, $defaultValue = null, $delete = false)
+ {
+ $counters = $this->get($this->flashParam, []);
+ if (isset($counters[$key])) {
+ $value = $this->get($key, $defaultValue);
+ if ($delete) {
+ $this->removeFlash($key);
+ }
+
+ return $value;
+ } else {
+ return $defaultValue;
+ }
+ }
+
+ /**
+ * Returns all flash messages.
+ * @return array flash messages (key => message).
+ */
+ public function getAllFlashes()
+ {
+ $counters = $this->get($this->flashParam, []);
+ $flashes = [];
+ foreach (array_keys($counters) as $key) {
+ if (isset($_SESSION[$key])) {
+ $flashes[$key] = $_SESSION[$key];
+ }
+ }
+
+ return $flashes;
+ }
+
+ /**
+ * Stores a flash message.
+ * A flash message is available only in the current request and the next request.
+ * @param string $key the key identifying the flash message. Note that flash messages
+ * and normal session variables share the same name space. If you have a normal
+ * session variable using the same name, its value will be overwritten by this method.
+ * @param mixed $value flash message
+ */
+ public function setFlash($key, $value = true)
+ {
+ $counters = $this->get($this->flashParam, []);
+ $counters[$key] = 0;
+ $_SESSION[$key] = $value;
+ $_SESSION[$this->flashParam] = $counters;
+ }
+
+ /**
+ * Removes a flash message.
+ * Note that flash messages will be automatically removed after the next request.
+ * @param string $key the key identifying the flash message. Note that flash messages
+ * and normal session variables share the same name space. If you have a normal
+ * session variable using the same name, it will be removed by this method.
+ * @return mixed the removed flash message. Null if the flash message does not exist.
+ */
+ public function removeFlash($key)
+ {
+ $counters = $this->get($this->flashParam, []);
+ $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
+ unset($counters[$key], $_SESSION[$key]);
+ $_SESSION[$this->flashParam] = $counters;
+
+ return $value;
+ }
+
+ /**
+ * Removes all flash messages.
+ * Note that flash messages and normal session variables share the same name space.
+ * If you have a normal session variable using the same name, it will be removed
+ * by this method.
+ */
+ public function removeAllFlashes()
+ {
+ $counters = $this->get($this->flashParam, []);
+ foreach (array_keys($counters) as $key) {
+ unset($_SESSION[$key]);
+ }
+ unset($_SESSION[$this->flashParam]);
+ }
+
+ /**
+ * Returns a value indicating whether there is a flash message associated with the specified key.
+ * @param string $key key identifying the flash message
+ * @return boolean whether the specified flash message exists
+ */
+ public function hasFlash($key)
+ {
+ return $this->getFlash($key) !== null;
+ }
+
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to check on
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ $this->open();
+
+ return isset($_SESSION[$offset]);
+ }
+
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param integer $offset the offset to retrieve element.
+ * @return mixed the element at the offset, null if no element is found at the offset
+ */
+ public function offsetGet($offset)
+ {
+ $this->open();
+
+ return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
+ }
+
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param integer $offset the offset to set element
+ * @param mixed $item the element value
+ */
+ public function offsetSet($offset, $item)
+ {
+ $this->open();
+ $_SESSION[$offset] = $item;
+ }
+
+ /**
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to unset element
+ */
+ public function offsetUnset($offset)
+ {
+ $this->open();
+ unset($_SESSION[$offset]);
+ }
}
diff --git a/framework/web/SessionIterator.php b/framework/web/SessionIterator.php
index c960dd45e48..7e4c9c0a577 100644
--- a/framework/web/SessionIterator.php
+++ b/framework/web/SessionIterator.php
@@ -15,70 +15,70 @@
*/
class SessionIterator implements \Iterator
{
- /**
- * @var array list of keys in the map
- */
- private $_keys;
- /**
- * @var mixed current key
- */
- private $_key;
+ /**
+ * @var array list of keys in the map
+ */
+ private $_keys;
+ /**
+ * @var mixed current key
+ */
+ private $_key;
- /**
- * Constructor.
- */
- public function __construct()
- {
- $this->_keys = array_keys($_SESSION);
- }
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_keys = array_keys($_SESSION);
+ }
- /**
- * Rewinds internal array pointer.
- * This method is required by the interface Iterator.
- */
- public function rewind()
- {
- $this->_key = reset($this->_keys);
- }
+ /**
+ * Rewinds internal array pointer.
+ * This method is required by the interface Iterator.
+ */
+ public function rewind()
+ {
+ $this->_key = reset($this->_keys);
+ }
- /**
- * Returns the key of the current array element.
- * This method is required by the interface Iterator.
- * @return mixed the key of the current array element
- */
- public function key()
- {
- return $this->_key;
- }
+ /**
+ * Returns the key of the current array element.
+ * This method is required by the interface Iterator.
+ * @return mixed the key of the current array element
+ */
+ public function key()
+ {
+ return $this->_key;
+ }
- /**
- * Returns the current array element.
- * This method is required by the interface Iterator.
- * @return mixed the current array element
- */
- public function current()
- {
- return isset($_SESSION[$this->_key]) ? $_SESSION[$this->_key] : null;
- }
+ /**
+ * Returns the current array element.
+ * This method is required by the interface Iterator.
+ * @return mixed the current array element
+ */
+ public function current()
+ {
+ return isset($_SESSION[$this->_key]) ? $_SESSION[$this->_key] : null;
+ }
- /**
- * Moves the internal pointer to the next array element.
- * This method is required by the interface Iterator.
- */
- public function next()
- {
- do {
- $this->_key = next($this->_keys);
- } while (!isset($_SESSION[$this->_key]) && $this->_key !== false);
- }
+ /**
+ * Moves the internal pointer to the next array element.
+ * This method is required by the interface Iterator.
+ */
+ public function next()
+ {
+ do {
+ $this->_key = next($this->_keys);
+ } while (!isset($_SESSION[$this->_key]) && $this->_key !== false);
+ }
- /**
- * Returns whether there is an element at current position.
- * This method is required by the interface Iterator.
- * @return boolean
- */
- public function valid()
- {
- return $this->_key !== false;
- }
+ /**
+ * Returns whether there is an element at current position.
+ * This method is required by the interface Iterator.
+ * @return boolean
+ */
+ public function valid()
+ {
+ return $this->_key !== false;
+ }
}
diff --git a/framework/web/TooManyRequestsHttpException.php b/framework/web/TooManyRequestsHttpException.php
index b5eb8898a5e..ebaa6c656b7 100644
--- a/framework/web/TooManyRequestsHttpException.php
+++ b/framework/web/TooManyRequestsHttpException.php
@@ -20,14 +20,14 @@
*/
class TooManyRequestsHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(429, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(429, $message, $code, $previous);
+ }
}
diff --git a/framework/web/UnauthorizedHttpException.php b/framework/web/UnauthorizedHttpException.php
index 0bea2090465..b8f84192aa2 100644
--- a/framework/web/UnauthorizedHttpException.php
+++ b/framework/web/UnauthorizedHttpException.php
@@ -21,14 +21,14 @@
*/
class UnauthorizedHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(401, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(401, $message, $code, $previous);
+ }
}
diff --git a/framework/web/UnsupportedMediaTypeHttpException.php b/framework/web/UnsupportedMediaTypeHttpException.php
index 715117e0ed3..d79ed4a5d82 100644
--- a/framework/web/UnsupportedMediaTypeHttpException.php
+++ b/framework/web/UnsupportedMediaTypeHttpException.php
@@ -21,14 +21,14 @@
*/
class UnsupportedMediaTypeHttpException extends HttpException
{
- /**
- * Constructor.
- * @param string $message error message
- * @param integer $code error code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = null, $code = 0, \Exception $previous = null)
- {
- parent::__construct(415, $message, $code, $previous);
- }
+ /**
+ * Constructor.
+ * @param string $message error message
+ * @param integer $code error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message = null, $code = 0, \Exception $previous = null)
+ {
+ parent::__construct(415, $message, $code, $previous);
+ }
}
diff --git a/framework/web/UploadedFile.php b/framework/web/UploadedFile.php
index dd2bb6157ad..0f9eaaec27f 100644
--- a/framework/web/UploadedFile.php
+++ b/framework/web/UploadedFile.php
@@ -28,209 +28,214 @@
*/
class UploadedFile extends Object
{
- private static $_files;
-
- /**
- * @var string the original name of the file being uploaded
- */
- public $name;
- /**
- * @var string the path of the uploaded file on the server.
- * Note, this is a temporary file which will be automatically deleted by PHP
- * after the current request is processed.
- */
- public $tempName;
- /**
- * @var string the MIME-type of the uploaded file (such as "image/gif").
- * Since this MIME type is not checked on the server side, do not take this value for granted.
- * Instead, use [[FileHelper::getMimeType()]] to determine the exact MIME type.
- */
- public $type;
- /**
- * @var integer the actual size of the uploaded file in bytes
- */
- public $size;
- /**
- * @var integer an error code describing the status of this file uploading.
- * @see http://www.php.net/manual/en/features.file-upload.errors.php
- */
- public $error;
-
-
- /**
- * String output.
- * This is PHP magic method that returns string representation of an object.
- * The implementation here returns the uploaded file's name.
- * @return string the string representation of the object
- */
- public function __toString()
- {
- return $this->name;
- }
-
- /**
- * Returns an uploaded file for the given model attribute.
- * The file should be uploaded using [[ActiveForm::fileInput()]].
- * @param \yii\base\Model $model the data model
- * @param string $attribute the attribute name. The attribute name may contain array indexes.
- * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
- * @return UploadedFile the instance of the uploaded file.
- * Null is returned if no file is uploaded for the specified model attribute.
- * @see getInstanceByName()
- */
- public static function getInstance($model, $attribute)
- {
- $name = Html::getInputName($model, $attribute);
- return static::getInstanceByName($name);
- }
-
- /**
- * Returns all uploaded files for the given model attribute.
- * @param \yii\base\Model $model the data model
- * @param string $attribute the attribute name. The attribute name may contain array indexes
- * for tabular file uploading, e.g. '[1]file'.
- * @return UploadedFile[] array of UploadedFile objects.
- * Empty array is returned if no available file was found for the given attribute.
- */
- public static function getInstances($model, $attribute)
- {
- $name = Html::getInputName($model, $attribute);
- return static::getInstancesByName($name);
- }
-
- /**
- * Returns an uploaded file according to the given file input name.
- * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
- * @param string $name the name of the file input field.
- * @return UploadedFile the instance of the uploaded file.
- * Null is returned if no file is uploaded for the specified name.
- */
- public static function getInstanceByName($name)
- {
- $files = static::loadFiles();
- return isset($files[$name]) ? $files[$name] : null;
- }
-
- /**
- * Returns an array of uploaded files corresponding to the specified file input name.
- * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
- * 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
- * @param string $name the name of the array of files
- * @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned
- * if no adequate upload was found. Please note that this array will contain
- * all files from all sub-arrays regardless how deeply nested they are.
- */
- public static function getInstancesByName($name)
- {
- $files = static::loadFiles();
- if (isset($files[$name])) {
- return [$files[$name]];
- }
- $results = [];
- foreach ($files as $key => $file) {
- if (strpos($key, "{$name}[") === 0) {
- $results[] = self::$_files[$key];
- }
- }
- return $results;
- }
-
- /**
- * Cleans up the loaded UploadedFile instances.
- * This method is mainly used by test scripts to set up a fixture.
- */
- public static function reset()
- {
- self::$_files = null;
- }
-
- /**
- * Saves the uploaded file.
- * Note that this method uses php's move_uploaded_file() method. If the target file `$file`
- * already exists, it will be overwritten.
- * @param string $file the file path used to save the uploaded file
- * @param boolean $deleteTempFile whether to delete the temporary file after saving.
- * If true, you will not be able to save the uploaded file again in the current request.
- * @return boolean true whether the file is saved successfully
- * @see error
- */
- public function saveAs($file, $deleteTempFile = true)
- {
- if ($this->error == UPLOAD_ERR_OK) {
- if ($deleteTempFile) {
- return move_uploaded_file($this->tempName, $file);
- } elseif (is_uploaded_file($this->tempName)) {
- return copy($this->tempName, $file);
- }
- }
- return false;
- }
-
- /**
- * @return string original file base name
- */
- public function getBaseName()
- {
- return pathinfo($this->name, PATHINFO_FILENAME);
- }
-
- /**
- * @return string file extension
- */
- public function getExtension()
- {
- return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
- }
-
- /**
- * @return boolean whether there is an error with the uploaded file.
- * Check [[error]] for detailed error code information.
- */
- public function getHasError()
- {
- return $this->error != UPLOAD_ERR_OK;
- }
-
- /**
- * Creates UploadedFile instances from $_FILE.
- * @return array the UploadedFile instances
- */
- private static function loadFiles()
- {
- if (self::$_files === null) {
- self::$_files = [];
- if (isset($_FILES) && is_array($_FILES)) {
- foreach ($_FILES as $class => $info) {
- self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
- }
- }
- }
- return self::$_files;
- }
-
- /**
- * Creates UploadedFile instances from $_FILE recursively.
- * @param string $key key for identifying uploaded file: class name and sub-array indexes
- * @param mixed $names file names provided by PHP
- * @param mixed $tempNames temporary file names provided by PHP
- * @param mixed $types file types provided by PHP
- * @param mixed $sizes file sizes provided by PHP
- * @param mixed $errors uploading issues provided by PHP
- */
- private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors)
- {
- if (is_array($names)) {
- foreach ($names as $i => $name) {
- self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
- }
- } else {
- self::$_files[$key] = new static([
- 'name' => $names,
- 'tempName' => $tempNames,
- 'type' => $types,
- 'size' => $sizes,
- 'error' => $errors,
- ]);
- }
- }
+ private static $_files;
+
+ /**
+ * @var string the original name of the file being uploaded
+ */
+ public $name;
+ /**
+ * @var string the path of the uploaded file on the server.
+ * Note, this is a temporary file which will be automatically deleted by PHP
+ * after the current request is processed.
+ */
+ public $tempName;
+ /**
+ * @var string the MIME-type of the uploaded file (such as "image/gif").
+ * Since this MIME type is not checked on the server side, do not take this value for granted.
+ * Instead, use [[FileHelper::getMimeType()]] to determine the exact MIME type.
+ */
+ public $type;
+ /**
+ * @var integer the actual size of the uploaded file in bytes
+ */
+ public $size;
+ /**
+ * @var integer an error code describing the status of this file uploading.
+ * @see http://www.php.net/manual/en/features.file-upload.errors.php
+ */
+ public $error;
+
+ /**
+ * String output.
+ * This is PHP magic method that returns string representation of an object.
+ * The implementation here returns the uploaded file's name.
+ * @return string the string representation of the object
+ */
+ public function __toString()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns an uploaded file for the given model attribute.
+ * The file should be uploaded using [[ActiveForm::fileInput()]].
+ * @param \yii\base\Model $model the data model
+ * @param string $attribute the attribute name. The attribute name may contain array indexes.
+ * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
+ * @return UploadedFile the instance of the uploaded file.
+ * Null is returned if no file is uploaded for the specified model attribute.
+ * @see getInstanceByName()
+ */
+ public static function getInstance($model, $attribute)
+ {
+ $name = Html::getInputName($model, $attribute);
+
+ return static::getInstanceByName($name);
+ }
+
+ /**
+ * Returns all uploaded files for the given model attribute.
+ * @param \yii\base\Model $model the data model
+ * @param string $attribute the attribute name. The attribute name may contain array indexes
+ * for tabular file uploading, e.g. '[1]file'.
+ * @return UploadedFile[] array of UploadedFile objects.
+ * Empty array is returned if no available file was found for the given attribute.
+ */
+ public static function getInstances($model, $attribute)
+ {
+ $name = Html::getInputName($model, $attribute);
+
+ return static::getInstancesByName($name);
+ }
+
+ /**
+ * Returns an uploaded file according to the given file input name.
+ * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
+ * @param string $name the name of the file input field.
+ * @return UploadedFile the instance of the uploaded file.
+ * Null is returned if no file is uploaded for the specified name.
+ */
+ public static function getInstanceByName($name)
+ {
+ $files = static::loadFiles();
+
+ return isset($files[$name]) ? $files[$name] : null;
+ }
+
+ /**
+ * Returns an array of uploaded files corresponding to the specified file input name.
+ * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
+ * 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
+ * @param string $name the name of the array of files
+ * @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned
+ * if no adequate upload was found. Please note that this array will contain
+ * all files from all sub-arrays regardless how deeply nested they are.
+ */
+ public static function getInstancesByName($name)
+ {
+ $files = static::loadFiles();
+ if (isset($files[$name])) {
+ return [$files[$name]];
+ }
+ $results = [];
+ foreach ($files as $key => $file) {
+ if (strpos($key, "{$name}[") === 0) {
+ $results[] = self::$_files[$key];
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Cleans up the loaded UploadedFile instances.
+ * This method is mainly used by test scripts to set up a fixture.
+ */
+ public static function reset()
+ {
+ self::$_files = null;
+ }
+
+ /**
+ * Saves the uploaded file.
+ * Note that this method uses php's move_uploaded_file() method. If the target file `$file`
+ * already exists, it will be overwritten.
+ * @param string $file the file path used to save the uploaded file
+ * @param boolean $deleteTempFile whether to delete the temporary file after saving.
+ * If true, you will not be able to save the uploaded file again in the current request.
+ * @return boolean true whether the file is saved successfully
+ * @see error
+ */
+ public function saveAs($file, $deleteTempFile = true)
+ {
+ if ($this->error == UPLOAD_ERR_OK) {
+ if ($deleteTempFile) {
+ return move_uploaded_file($this->tempName, $file);
+ } elseif (is_uploaded_file($this->tempName)) {
+ return copy($this->tempName, $file);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return string original file base name
+ */
+ public function getBaseName()
+ {
+ return pathinfo($this->name, PATHINFO_FILENAME);
+ }
+
+ /**
+ * @return string file extension
+ */
+ public function getExtension()
+ {
+ return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
+ }
+
+ /**
+ * @return boolean whether there is an error with the uploaded file.
+ * Check [[error]] for detailed error code information.
+ */
+ public function getHasError()
+ {
+ return $this->error != UPLOAD_ERR_OK;
+ }
+
+ /**
+ * Creates UploadedFile instances from $_FILE.
+ * @return array the UploadedFile instances
+ */
+ private static function loadFiles()
+ {
+ if (self::$_files === null) {
+ self::$_files = [];
+ if (isset($_FILES) && is_array($_FILES)) {
+ foreach ($_FILES as $class => $info) {
+ self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
+ }
+ }
+ }
+
+ return self::$_files;
+ }
+
+ /**
+ * Creates UploadedFile instances from $_FILE recursively.
+ * @param string $key key for identifying uploaded file: class name and sub-array indexes
+ * @param mixed $names file names provided by PHP
+ * @param mixed $tempNames temporary file names provided by PHP
+ * @param mixed $types file types provided by PHP
+ * @param mixed $sizes file sizes provided by PHP
+ * @param mixed $errors uploading issues provided by PHP
+ */
+ private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors)
+ {
+ if (is_array($names)) {
+ foreach ($names as $i => $name) {
+ self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
+ }
+ } else {
+ self::$_files[$key] = new static([
+ 'name' => $names,
+ 'tempName' => $tempNames,
+ 'type' => $types,
+ 'size' => $sizes,
+ 'error' => $errors,
+ ]);
+ }
+ }
}
diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php
index 0341d4aeb28..c335112e0f4 100644
--- a/framework/web/UrlManager.php
+++ b/framework/web/UrlManager.php
@@ -40,309 +40,316 @@
*/
class UrlManager extends Component
{
- /**
- * @var boolean whether to enable pretty URLs. Instead of putting all parameters in the query
- * string part of a URL, pretty URLs allow using path info to represent some of the parameters
- * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of
- * "/index.php?r=news/view&id=100".
- */
- public $enablePrettyUrl = false;
- /**
- * @var boolean whether to enable strict parsing. If strict parsing is enabled, the incoming
- * requested URL must match at least one of the [[rules]] in order to be treated as a valid request.
- * Otherwise, the path info part of the request will be treated as the requested route.
- * This property is used only when [[enablePrettyUrl]] is true.
- */
- public $enableStrictParsing = false;
- /**
- * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is true.
- * This property is used only if [[enablePrettyUrl]] is true. Each element in the array
- * is the configuration array for creating a single URL rule. The configuration will
- * be merged with [[ruleConfig]] first before it is used for creating the rule object.
- *
- * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
- * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
- * array, one can use the key to represent the pattern and the value the corresponding route.
- * For example, `'post/' => 'post/view'`.
- *
- * For RESTful routing the mentioned shortcut format also allows you to specify the
- * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
- * You can do that by prepending it to the pattern, separated by space.
- * For example, `'PUT post/' => 'post/update'`.
- * You may specify multiple verbs by separating them with comma
- * like this: `'POST,PUT post/index' => 'post/create'`.
- * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE.
- * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
- * so you normally would not specify a verb for normal GET request.
- *
- * Here is an example configuration for RESTful CRUD controller:
- *
- * ~~~php
- * [
- * 'dashboard' => 'site/index',
- *
- * 'POST s' => '/create',
- * 's' => '/index',
- *
- * 'PUT /' => '/update',
- * 'DELETE /' => '/delete',
- * '/' => '/view',
- * ];
- * ~~~
- *
- * Note that if you modify this property after the UrlManager object is created, make sure
- * you populate the array with rule objects instead of rule configurations.
- */
- public $rules = [];
- /**
- * @var string the URL suffix used when in 'path' format.
- * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
- * This property is used only if [[enablePrettyUrl]] is true.
- */
- public $suffix;
- /**
- * @var boolean whether to show entry script name in the constructed URL. Defaults to true.
- * This property is used only if [[enablePrettyUrl]] is true.
- */
- public $showScriptName = true;
- /**
- * @var string the GET parameter name for route. This property is used only if [[enablePrettyUrl]] is false.
- */
- public $routeParam = 'r';
- /**
- * @var Cache|string the cache object or the application component ID of the cache object.
- * Compiled URL rules will be cached through this cache object, if it is available.
- *
- * After the UrlManager object is created, if you want to change this property,
- * you should only assign it with a cache object.
- * Set this property to null if you do not want to cache the URL rules.
- */
- public $cache = 'cache';
- /**
- * @var array the default configuration of URL rules. Individual rule configurations
- * specified via [[rules]] will take precedence when the same property of the rule is configured.
- */
- public $ruleConfig = ['class' => 'yii\web\UrlRule'];
+ /**
+ * @var boolean whether to enable pretty URLs. Instead of putting all parameters in the query
+ * string part of a URL, pretty URLs allow using path info to represent some of the parameters
+ * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of
+ * "/index.php?r=news/view&id=100".
+ */
+ public $enablePrettyUrl = false;
+ /**
+ * @var boolean whether to enable strict parsing. If strict parsing is enabled, the incoming
+ * requested URL must match at least one of the [[rules]] in order to be treated as a valid request.
+ * Otherwise, the path info part of the request will be treated as the requested route.
+ * This property is used only when [[enablePrettyUrl]] is true.
+ */
+ public $enableStrictParsing = false;
+ /**
+ * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is true.
+ * This property is used only if [[enablePrettyUrl]] is true. Each element in the array
+ * is the configuration array for creating a single URL rule. The configuration will
+ * be merged with [[ruleConfig]] first before it is used for creating the rule object.
+ *
+ * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
+ * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
+ * array, one can use the key to represent the pattern and the value the corresponding route.
+ * For example, `'post/' => 'post/view'`.
+ *
+ * For RESTful routing the mentioned shortcut format also allows you to specify the
+ * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
+ * You can do that by prepending it to the pattern, separated by space.
+ * For example, `'PUT post/' => 'post/update'`.
+ * You may specify multiple verbs by separating them with comma
+ * like this: `'POST,PUT post/index' => 'post/create'`.
+ * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE.
+ * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
+ * so you normally would not specify a verb for normal GET request.
+ *
+ * Here is an example configuration for RESTful CRUD controller:
+ *
+ * ~~~php
+ * [
+ * 'dashboard' => 'site/index',
+ *
+ * 'POST s' => '/create',
+ * 's' => '/index',
+ *
+ * 'PUT /' => '/update',
+ * 'DELETE /' => '/delete',
+ * '/' => '/view',
+ * ];
+ * ~~~
+ *
+ * Note that if you modify this property after the UrlManager object is created, make sure
+ * you populate the array with rule objects instead of rule configurations.
+ */
+ public $rules = [];
+ /**
+ * @var string the URL suffix used when in 'path' format.
+ * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
+ * This property is used only if [[enablePrettyUrl]] is true.
+ */
+ public $suffix;
+ /**
+ * @var boolean whether to show entry script name in the constructed URL. Defaults to true.
+ * This property is used only if [[enablePrettyUrl]] is true.
+ */
+ public $showScriptName = true;
+ /**
+ * @var string the GET parameter name for route. This property is used only if [[enablePrettyUrl]] is false.
+ */
+ public $routeParam = 'r';
+ /**
+ * @var Cache|string the cache object or the application component ID of the cache object.
+ * Compiled URL rules will be cached through this cache object, if it is available.
+ *
+ * After the UrlManager object is created, if you want to change this property,
+ * you should only assign it with a cache object.
+ * Set this property to null if you do not want to cache the URL rules.
+ */
+ public $cache = 'cache';
+ /**
+ * @var array the default configuration of URL rules. Individual rule configurations
+ * specified via [[rules]] will take precedence when the same property of the rule is configured.
+ */
+ public $ruleConfig = ['class' => 'yii\web\UrlRule'];
- private $_baseUrl;
- private $_hostInfo;
+ private $_baseUrl;
+ private $_hostInfo;
- /**
- * Initializes UrlManager.
- */
- public function init()
- {
- parent::init();
- $this->compileRules();
- }
+ /**
+ * Initializes UrlManager.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->compileRules();
+ }
- /**
- * Parses the URL rules.
- */
- protected function compileRules()
- {
- if (!$this->enablePrettyUrl || empty($this->rules)) {
- return;
- }
- if (is_string($this->cache)) {
- $this->cache = Yii::$app->getComponent($this->cache);
- }
- if ($this->cache instanceof Cache) {
- $key = __CLASS__;
- $hash = md5(json_encode($this->rules));
- if (($data = $this->cache->get($key)) !== false && isset($data[1]) && $data[1] === $hash) {
- $this->rules = $data[0];
- return;
- }
- }
+ /**
+ * Parses the URL rules.
+ */
+ protected function compileRules()
+ {
+ if (!$this->enablePrettyUrl || empty($this->rules)) {
+ return;
+ }
+ if (is_string($this->cache)) {
+ $this->cache = Yii::$app->getComponent($this->cache);
+ }
+ if ($this->cache instanceof Cache) {
+ $key = __CLASS__;
+ $hash = md5(json_encode($this->rules));
+ if (($data = $this->cache->get($key)) !== false && isset($data[1]) && $data[1] === $hash) {
+ $this->rules = $data[0];
- $rules = [];
- $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
- foreach ($this->rules as $key => $rule) {
- if (!is_array($rule)) {
- $rule = ['route' => $rule];
- if (preg_match("/^((?:($verbs),)*($verbs))\\s+(.*)$/", $key, $matches)) {
- $rule['verb'] = explode(',', $matches[1]);
- $rule['mode'] = UrlRule::PARSING_ONLY;
- $key = $matches[4];
- }
- $rule['pattern'] = $key;
- }
- $rule = Yii::createObject(array_merge($this->ruleConfig, $rule));
- if (!$rule instanceof UrlRuleInterface) {
- throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.');
- }
- $rules[] = $rule;
- }
- $this->rules = $rules;
+ return;
+ }
+ }
- if (isset($key, $hash)) {
- $this->cache->set($key, [$this->rules, $hash]);
- }
- }
+ $rules = [];
+ $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
+ foreach ($this->rules as $key => $rule) {
+ if (!is_array($rule)) {
+ $rule = ['route' => $rule];
+ if (preg_match("/^((?:($verbs),)*($verbs))\\s+(.*)$/", $key, $matches)) {
+ $rule['verb'] = explode(',', $matches[1]);
+ $rule['mode'] = UrlRule::PARSING_ONLY;
+ $key = $matches[4];
+ }
+ $rule['pattern'] = $key;
+ }
+ $rule = Yii::createObject(array_merge($this->ruleConfig, $rule));
+ if (!$rule instanceof UrlRuleInterface) {
+ throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.');
+ }
+ $rules[] = $rule;
+ }
+ $this->rules = $rules;
- /**
- * Parses the user request.
- * @param Request $request the request component
- * @return array|boolean the route and the associated parameters. The latter is always empty
- * if [[enablePrettyUrl]] is false. False is returned if the current request cannot be successfully parsed.
- */
- public function parseRequest($request)
- {
- if ($this->enablePrettyUrl) {
- $pathInfo = $request->getPathInfo();
- /** @var UrlRule $rule */
- foreach ($this->rules as $rule) {
- if (($result = $rule->parseRequest($this, $request)) !== false) {
- return $result;
- }
- }
+ if (isset($key, $hash)) {
+ $this->cache->set($key, [$this->rules, $hash]);
+ }
+ }
- if ($this->enableStrictParsing) {
- return false;
- }
+ /**
+ * Parses the user request.
+ * @param Request $request the request component
+ * @return array|boolean the route and the associated parameters. The latter is always empty
+ * if [[enablePrettyUrl]] is false. False is returned if the current request cannot be successfully parsed.
+ */
+ public function parseRequest($request)
+ {
+ if ($this->enablePrettyUrl) {
+ $pathInfo = $request->getPathInfo();
+ /** @var UrlRule $rule */
+ foreach ($this->rules as $rule) {
+ if (($result = $rule->parseRequest($this, $request)) !== false) {
+ return $result;
+ }
+ }
- Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__);
+ if ($this->enableStrictParsing) {
+ return false;
+ }
- $suffix = (string)$this->suffix;
- if ($suffix !== '' && $pathInfo !== '') {
- $n = strlen($this->suffix);
- if (substr($pathInfo, -$n) === $this->suffix) {
- $pathInfo = substr($pathInfo, 0, -$n);
- if ($pathInfo === '') {
- // suffix alone is not allowed
- return false;
- }
- } else {
- // suffix doesn't match
- return false;
- }
- }
+ Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__);
- return [$pathInfo, []];
- } else {
- Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
- $route = $request->getQueryParam($this->routeParam, '');
- if (is_array($route)) {
- $route = '';
- }
- return [(string)$route, []];
- }
- }
+ $suffix = (string) $this->suffix;
+ if ($suffix !== '' && $pathInfo !== '') {
+ $n = strlen($this->suffix);
+ if (substr($pathInfo, -$n) === $this->suffix) {
+ $pathInfo = substr($pathInfo, 0, -$n);
+ if ($pathInfo === '') {
+ // suffix alone is not allowed
+ return false;
+ }
+ } else {
+ // suffix doesn't match
+ return false;
+ }
+ }
- /**
- * Creates a URL using the given route and parameters.
- * The URL created is a relative one. Use [[createAbsoluteUrl()]] to create an absolute URL.
- * @param string|array $params route as a string or route and parameters in form of ['route', 'param1' => 'value1', 'param2' => 'value2']
- * @return string the created URL
- */
- public function createUrl($params)
- {
- $params = (array)$params;
- $anchor = isset($params['#']) ? '#' . $params['#'] : '';
- unset($params['#'], $params[$this->routeParam]);
+ return [$pathInfo, []];
+ } else {
+ Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
+ $route = $request->getQueryParam($this->routeParam, '');
+ if (is_array($route)) {
+ $route = '';
+ }
- $route = trim($params[0], '/');
- unset($params[0]);
- $baseUrl = $this->getBaseUrl();
+ return [(string) $route, []];
+ }
+ }
- if ($this->enablePrettyUrl) {
- /** @var UrlRule $rule */
- foreach ($this->rules as $rule) {
- if (($url = $rule->createUrl($this, $route, $params)) !== false) {
- if (strpos($url, '://') !== false) {
- if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
- return substr($url, 0, $pos) . $baseUrl . substr($url, $pos);
- } else {
- return $url . $baseUrl . $anchor;
- }
- } else {
- return "$baseUrl/{$url}{$anchor}";
- }
- }
- }
+ /**
+ * Creates a URL using the given route and parameters.
+ * The URL created is a relative one. Use [[createAbsoluteUrl()]] to create an absolute URL.
+ * @param string|array $params route as a string or route and parameters in form of ['route', 'param1' => 'value1', 'param2' => 'value2']
+ * @return string the created URL
+ */
+ public function createUrl($params)
+ {
+ $params = (array) $params;
+ $anchor = isset($params['#']) ? '#' . $params['#'] : '';
+ unset($params['#'], $params[$this->routeParam]);
- if ($this->suffix !== null) {
- $route .= $this->suffix;
- }
- if (!empty($params) && ($query = http_build_query($params)) !== '') {
- $route .= '?' . $query;
- }
- return "$baseUrl/{$route}{$anchor}";
- } else {
- $url = "$baseUrl?{$this->routeParam}=$route";
- if (!empty($params) && ($query = http_build_query($params)) !== '') {
- $url .= '&' . $query;
- }
- return $url . $anchor;
- }
- }
+ $route = trim($params[0], '/');
+ unset($params[0]);
+ $baseUrl = $this->getBaseUrl();
- /**
- * Creates an absolute URL using the given route and parameters.
- * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]].
- * @param string|array $params route as a string or route and parameters in form of ['route', 'param1' => 'value1', 'param2' => 'value2']
- * @param string $schema the schema to use for the url. e.g. 'http' or 'https'. If not specified
- * the schema of the current request will be used.
- * @return string the created URL
- * @see createUrl()
- */
- public function createAbsoluteUrl($params, $schema = null)
- {
- $params = (array)$params;
- $url = $this->createUrl($params);
- if (strpos($url, '://') === false) {
- $url = $this->getHostInfo() . $url;
- }
- if ($schema && ($pos = strpos($url, '://')) !== false) {
- $url = $schema . substr($url, $pos);
- }
- return $url;
- }
+ if ($this->enablePrettyUrl) {
+ /** @var UrlRule $rule */
+ foreach ($this->rules as $rule) {
+ if (($url = $rule->createUrl($this, $route, $params)) !== false) {
+ if (strpos($url, '://') !== false) {
+ if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
+ return substr($url, 0, $pos) . $baseUrl . substr($url, $pos);
+ } else {
+ return $url . $baseUrl . $anchor;
+ }
+ } else {
+ return "$baseUrl/{$url}{$anchor}";
+ }
+ }
+ }
- /**
- * Returns the base URL that is used by [[createUrl()]] to prepend URLs it creates.
- * It defaults to [[Request::scriptUrl]] if [[showScriptName]] is true or [[enablePrettyUrl]] is false;
- * otherwise, it defaults to [[Request::baseUrl]].
- * @return string the base URL that is used by [[createUrl()]] to prepend URLs it creates.
- */
- public function getBaseUrl()
- {
- if ($this->_baseUrl === null) {
- /** @var \yii\web\Request $request */
- $request = Yii::$app->getRequest();
- $this->_baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $request->getScriptUrl() : $request->getBaseUrl();
- }
- return $this->_baseUrl;
- }
+ if ($this->suffix !== null) {
+ $route .= $this->suffix;
+ }
+ if (!empty($params) && ($query = http_build_query($params)) !== '') {
+ $route .= '?' . $query;
+ }
- /**
- * Sets the base URL that is used by [[createUrl()]] to prepend URLs it creates.
- * @param string $value the base URL that is used by [[createUrl()]] to prepend URLs it creates.
- */
- public function setBaseUrl($value)
- {
- $this->_baseUrl = rtrim($value, '/');
- }
+ return "$baseUrl/{$route}{$anchor}";
+ } else {
+ $url = "$baseUrl?{$this->routeParam}=$route";
+ if (!empty($params) && ($query = http_build_query($params)) !== '') {
+ $url .= '&' . $query;
+ }
- /**
- * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
- * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
- */
- public function getHostInfo()
- {
- if ($this->_hostInfo === null) {
- $this->_hostInfo = Yii::$app->getRequest()->getHostInfo();
- }
- return $this->_hostInfo;
- }
+ return $url . $anchor;
+ }
+ }
- /**
- * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
- * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
- */
- public function setHostInfo($value)
- {
- $this->_hostInfo = rtrim($value, '/');
- }
+ /**
+ * Creates an absolute URL using the given route and parameters.
+ * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]].
+ * @param string|array $params route as a string or route and parameters in form of ['route', 'param1' => 'value1', 'param2' => 'value2']
+ * @param string $schema the schema to use for the url. e.g. 'http' or 'https'. If not specified
+ * the schema of the current request will be used.
+ * @return string the created URL
+ * @see createUrl()
+ */
+ public function createAbsoluteUrl($params, $schema = null)
+ {
+ $params = (array) $params;
+ $url = $this->createUrl($params);
+ if (strpos($url, '://') === false) {
+ $url = $this->getHostInfo() . $url;
+ }
+ if ($schema && ($pos = strpos($url, '://')) !== false) {
+ $url = $schema . substr($url, $pos);
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+ * It defaults to [[Request::scriptUrl]] if [[showScriptName]] is true or [[enablePrettyUrl]] is false;
+ * otherwise, it defaults to [[Request::baseUrl]].
+ * @return string the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+ */
+ public function getBaseUrl()
+ {
+ if ($this->_baseUrl === null) {
+ /** @var \yii\web\Request $request */
+ $request = Yii::$app->getRequest();
+ $this->_baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $request->getScriptUrl() : $request->getBaseUrl();
+ }
+
+ return $this->_baseUrl;
+ }
+
+ /**
+ * Sets the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+ * @param string $value the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+ */
+ public function setBaseUrl($value)
+ {
+ $this->_baseUrl = rtrim($value, '/');
+ }
+
+ /**
+ * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+ * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+ */
+ public function getHostInfo()
+ {
+ if ($this->_hostInfo === null) {
+ $this->_hostInfo = Yii::$app->getRequest()->getHostInfo();
+ }
+
+ return $this->_hostInfo;
+ }
+
+ /**
+ * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+ * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+ */
+ public function setHostInfo($value)
+ {
+ $this->_hostInfo = rtrim($value, '/');
+ }
}
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index 7b671f5f4d5..d925fc3ba49 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -29,309 +29,311 @@
*/
class UrlRule extends Object implements UrlRuleInterface
{
- /**
- * Set [[mode]] with this value to mark that this rule is for URL parsing only
- */
- const PARSING_ONLY = 1;
- /**
- * Set [[mode]] with this value to mark that this rule is for URL creation only
- */
- const CREATION_ONLY = 2;
+ /**
+ * Set [[mode]] with this value to mark that this rule is for URL parsing only
+ */
+ const PARSING_ONLY = 1;
+ /**
+ * Set [[mode]] with this value to mark that this rule is for URL creation only
+ */
+ const CREATION_ONLY = 2;
- /**
- * @var string the name of this rule. If not set, it will use [[pattern]] as the name.
- */
- public $name;
- /**
- * @var string the pattern used to parse and create the path info part of a URL.
- * @see host
- */
- public $pattern;
- /**
- * @var string the pattern used to parse and create the host info part of a URL (e.g. `http://example.com`).
- * @see pattern
- */
- public $host;
- /**
- * @var string the route to the controller action
- */
- public $route;
- /**
- * @var array the default GET parameters (name => value) that this rule provides.
- * When this rule is used to parse the incoming request, the values declared in this property
- * will be injected into $_GET.
- */
- public $defaults = [];
- /**
- * @var string the URL suffix used for this rule.
- * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
- * If not, the value of [[UrlManager::suffix]] will be used.
- */
- public $suffix;
- /**
- * @var string|array the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
- * Use array to represent multiple verbs that this rule may match.
- * If this property is not set, the rule can match any verb.
- * Note that this property is only used when parsing a request. It is ignored for URL creation.
- */
- public $verb;
- /**
- * @var integer a value indicating if this rule should be used for both request parsing and URL creation,
- * parsing only, or creation only.
- * If not set or 0, it means the rule is both request parsing and URL creation.
- * If it is [[PARSING_ONLY]], the rule is for request parsing only.
- * If it is [[CREATION_ONLY]], the rule is for URL creation only.
- */
- public $mode;
+ /**
+ * @var string the name of this rule. If not set, it will use [[pattern]] as the name.
+ */
+ public $name;
+ /**
+ * @var string the pattern used to parse and create the path info part of a URL.
+ * @see host
+ */
+ public $pattern;
+ /**
+ * @var string the pattern used to parse and create the host info part of a URL (e.g. `http://example.com`).
+ * @see pattern
+ */
+ public $host;
+ /**
+ * @var string the route to the controller action
+ */
+ public $route;
+ /**
+ * @var array the default GET parameters (name => value) that this rule provides.
+ * When this rule is used to parse the incoming request, the values declared in this property
+ * will be injected into $_GET.
+ */
+ public $defaults = [];
+ /**
+ * @var string the URL suffix used for this rule.
+ * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
+ * If not, the value of [[UrlManager::suffix]] will be used.
+ */
+ public $suffix;
+ /**
+ * @var string|array the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
+ * Use array to represent multiple verbs that this rule may match.
+ * If this property is not set, the rule can match any verb.
+ * Note that this property is only used when parsing a request. It is ignored for URL creation.
+ */
+ public $verb;
+ /**
+ * @var integer a value indicating if this rule should be used for both request parsing and URL creation,
+ * parsing only, or creation only.
+ * If not set or 0, it means the rule is both request parsing and URL creation.
+ * If it is [[PARSING_ONLY]], the rule is for request parsing only.
+ * If it is [[CREATION_ONLY]], the rule is for URL creation only.
+ */
+ public $mode;
- /**
- * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
- */
- private $_template;
- /**
- * @var string the regex for matching the route part. This is used in generating URL.
- */
- private $_routeRule;
- /**
- * @var array list of regex for matching parameters. This is used in generating URL.
- */
- private $_paramRules = [];
- /**
- * @var array list of parameters used in the route.
- */
- private $_routeParams = [];
+ /**
+ * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
+ */
+ private $_template;
+ /**
+ * @var string the regex for matching the route part. This is used in generating URL.
+ */
+ private $_routeRule;
+ /**
+ * @var array list of regex for matching parameters. This is used in generating URL.
+ */
+ private $_paramRules = [];
+ /**
+ * @var array list of parameters used in the route.
+ */
+ private $_routeParams = [];
- /**
- * Initializes this rule.
- */
- public function init()
- {
- if ($this->pattern === null) {
- throw new InvalidConfigException('UrlRule::pattern must be set.');
- }
- if ($this->route === null) {
- throw new InvalidConfigException('UrlRule::route must be set.');
- }
- if ($this->verb !== null) {
- if (is_array($this->verb)) {
- foreach ($this->verb as $i => $verb) {
- $this->verb[$i] = strtoupper($verb);
- }
- } else {
- $this->verb = [strtoupper($this->verb)];
- }
- }
- if ($this->name === null) {
- $this->name = $this->pattern;
- }
+ /**
+ * Initializes this rule.
+ */
+ public function init()
+ {
+ if ($this->pattern === null) {
+ throw new InvalidConfigException('UrlRule::pattern must be set.');
+ }
+ if ($this->route === null) {
+ throw new InvalidConfigException('UrlRule::route must be set.');
+ }
+ if ($this->verb !== null) {
+ if (is_array($this->verb)) {
+ foreach ($this->verb as $i => $verb) {
+ $this->verb[$i] = strtoupper($verb);
+ }
+ } else {
+ $this->verb = [strtoupper($this->verb)];
+ }
+ }
+ if ($this->name === null) {
+ $this->name = $this->pattern;
+ }
- $this->pattern = trim($this->pattern, '/');
+ $this->pattern = trim($this->pattern, '/');
- if ($this->host !== null) {
- $this->host = rtrim($this->host, '/');
- $this->pattern = rtrim($this->host . '/' . $this->pattern, '/');
- } elseif ($this->pattern === '') {
- $this->_template = '';
- $this->pattern = '#^$#u';
- return;
- } elseif (($pos = strpos($this->pattern, '://')) !== false) {
- if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) {
- $this->host = substr($this->pattern, 0, $pos2);
- } else {
- $this->host = $this->pattern;
- }
- } else {
- $this->pattern = '/' . $this->pattern . '/';
- }
+ if ($this->host !== null) {
+ $this->host = rtrim($this->host, '/');
+ $this->pattern = rtrim($this->host . '/' . $this->pattern, '/');
+ } elseif ($this->pattern === '') {
+ $this->_template = '';
+ $this->pattern = '#^$#u';
- $this->route = trim($this->route, '/');
- if (strpos($this->route, '<') !== false && preg_match_all('/<(\w+)>/', $this->route, $matches)) {
- foreach ($matches[1] as $name) {
- $this->_routeParams[$name] = "<$name>";
- }
- }
+ return;
+ } elseif (($pos = strpos($this->pattern, '://')) !== false) {
+ if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) {
+ $this->host = substr($this->pattern, 0, $pos2);
+ } else {
+ $this->host = $this->pattern;
+ }
+ } else {
+ $this->pattern = '/' . $this->pattern . '/';
+ }
- $tr = [
- '.' => '\\.',
- '*' => '\\*',
- '$' => '\\$',
- '[' => '\\[',
- ']' => '\\]',
- '(' => '\\(',
- ')' => '\\)',
- ];
- $tr2 = [];
- if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
- foreach ($matches as $match) {
- $name = $match[1][0];
- $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
- if (array_key_exists($name, $this->defaults)) {
- $length = strlen($match[0][0]);
- $offset = $match[0][1];
- if ($offset > 1 && $this->pattern[$offset - 1] === '/' && $this->pattern[$offset + $length] === '/') {
- $tr["/<$name>"] = "(/(?P<$name>$pattern))?";
- } else {
- $tr["<$name>"] = "(?P<$name>$pattern)?";
- }
- } else {
- $tr["<$name>"] = "(?P<$name>$pattern)";
- }
- if (isset($this->_routeParams[$name])) {
- $tr2["<$name>"] = "(?P<$name>$pattern)";
- } else {
- $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#";
- }
- }
- }
+ $this->route = trim($this->route, '/');
+ if (strpos($this->route, '<') !== false && preg_match_all('/<(\w+)>/', $this->route, $matches)) {
+ foreach ($matches[1] as $name) {
+ $this->_routeParams[$name] = "<$name>";
+ }
+ }
- $this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);
- $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';
+ $tr = [
+ '.' => '\\.',
+ '*' => '\\*',
+ '$' => '\\$',
+ '[' => '\\[',
+ ']' => '\\]',
+ '(' => '\\(',
+ ')' => '\\)',
+ ];
+ $tr2 = [];
+ if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $name = $match[1][0];
+ $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
+ if (array_key_exists($name, $this->defaults)) {
+ $length = strlen($match[0][0]);
+ $offset = $match[0][1];
+ if ($offset > 1 && $this->pattern[$offset - 1] === '/' && $this->pattern[$offset + $length] === '/') {
+ $tr["/<$name>"] = "(/(?P<$name>$pattern))?";
+ } else {
+ $tr["<$name>"] = "(?P<$name>$pattern)?";
+ }
+ } else {
+ $tr["<$name>"] = "(?P<$name>$pattern)";
+ }
+ if (isset($this->_routeParams[$name])) {
+ $tr2["<$name>"] = "(?P<$name>$pattern)";
+ } else {
+ $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#";
+ }
+ }
+ }
- if (!empty($this->_routeParams)) {
- $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
- }
- }
+ $this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);
+ $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';
- /**
- * Parses the given request and returns the corresponding route and parameters.
- * @param UrlManager $manager the URL manager
- * @param Request $request the request component
- * @return array|boolean the parsing result. The route and the parameters are returned as an array.
- * If false, it means this rule cannot be used to parse this path info.
- */
- public function parseRequest($manager, $request)
- {
- if ($this->mode === self::CREATION_ONLY) {
- return false;
- }
+ if (!empty($this->_routeParams)) {
+ $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
+ }
+ }
- if (!empty($this->verb) && !in_array($request->getMethod(), $this->verb, true)) {
- return false;
- }
+ /**
+ * Parses the given request and returns the corresponding route and parameters.
+ * @param UrlManager $manager the URL manager
+ * @param Request $request the request component
+ * @return array|boolean the parsing result. The route and the parameters are returned as an array.
+ * If false, it means this rule cannot be used to parse this path info.
+ */
+ public function parseRequest($manager, $request)
+ {
+ if ($this->mode === self::CREATION_ONLY) {
+ return false;
+ }
- $pathInfo = $request->getPathInfo();
- $suffix = (string)($this->suffix === null ? $manager->suffix : $this->suffix);
- if ($suffix !== '' && $pathInfo !== '') {
- $n = strlen($suffix);
- if (substr($pathInfo, -$n) === $suffix) {
- $pathInfo = substr($pathInfo, 0, -$n);
- if ($pathInfo === '') {
- // suffix alone is not allowed
- return false;
- }
- } else {
- return false;
- }
- }
+ if (!empty($this->verb) && !in_array($request->getMethod(), $this->verb, true)) {
+ return false;
+ }
- if ($this->host !== null) {
- $pathInfo = strtolower($request->getHostInfo()) . ($pathInfo === '' ? '' : '/' . $pathInfo);
- }
+ $pathInfo = $request->getPathInfo();
+ $suffix = (string) ($this->suffix === null ? $manager->suffix : $this->suffix);
+ if ($suffix !== '' && $pathInfo !== '') {
+ $n = strlen($suffix);
+ if (substr($pathInfo, -$n) === $suffix) {
+ $pathInfo = substr($pathInfo, 0, -$n);
+ if ($pathInfo === '') {
+ // suffix alone is not allowed
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
- if (!preg_match($this->pattern, $pathInfo, $matches)) {
- return false;
- }
- foreach ($this->defaults as $name => $value) {
- if (!isset($matches[$name]) || $matches[$name] === '') {
- $matches[$name] = $value;
- }
- }
- $params = $this->defaults;
- $tr = [];
- foreach ($matches as $name => $value) {
- if (isset($this->_routeParams[$name])) {
- $tr[$this->_routeParams[$name]] = $value;
- unset($params[$name]);
- } elseif (isset($this->_paramRules[$name])) {
- $params[$name] = $value;
- }
- }
- if ($this->_routeRule !== null) {
- $route = strtr($this->route, $tr);
- } else {
- $route = $this->route;
- }
+ if ($this->host !== null) {
+ $pathInfo = strtolower($request->getHostInfo()) . ($pathInfo === '' ? '' : '/' . $pathInfo);
+ }
- Yii::trace("Request parsed with URL rule: {$this->name}", __METHOD__);
+ if (!preg_match($this->pattern, $pathInfo, $matches)) {
+ return false;
+ }
+ foreach ($this->defaults as $name => $value) {
+ if (!isset($matches[$name]) || $matches[$name] === '') {
+ $matches[$name] = $value;
+ }
+ }
+ $params = $this->defaults;
+ $tr = [];
+ foreach ($matches as $name => $value) {
+ if (isset($this->_routeParams[$name])) {
+ $tr[$this->_routeParams[$name]] = $value;
+ unset($params[$name]);
+ } elseif (isset($this->_paramRules[$name])) {
+ $params[$name] = $value;
+ }
+ }
+ if ($this->_routeRule !== null) {
+ $route = strtr($this->route, $tr);
+ } else {
+ $route = $this->route;
+ }
- return [$route, $params];
- }
+ Yii::trace("Request parsed with URL rule: {$this->name}", __METHOD__);
- /**
- * Creates a URL according to the given route and parameters.
- * @param UrlManager $manager the URL manager
- * @param string $route the route. It should not have slashes at the beginning or the end.
- * @param array $params the parameters
- * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL.
- */
- public function createUrl($manager, $route, $params)
- {
- if ($this->mode === self::PARSING_ONLY) {
- return false;
- }
+ return [$route, $params];
+ }
- $tr = [];
+ /**
+ * Creates a URL according to the given route and parameters.
+ * @param UrlManager $manager the URL manager
+ * @param string $route the route. It should not have slashes at the beginning or the end.
+ * @param array $params the parameters
+ * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL.
+ */
+ public function createUrl($manager, $route, $params)
+ {
+ if ($this->mode === self::PARSING_ONLY) {
+ return false;
+ }
- // match the route part first
- if ($route !== $this->route) {
- if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
- foreach ($this->_routeParams as $name => $token) {
- if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
- $tr[$token] = '';
- } else {
- $tr[$token] = $matches[$name];
- }
- }
- } else {
- return false;
- }
- }
+ $tr = [];
- // match default params
- // if a default param is not in the route pattern, its value must also be matched
- foreach ($this->defaults as $name => $value) {
- if (isset($this->_routeParams[$name])) {
- continue;
- }
- if (!isset($params[$name])) {
- return false;
- } elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
- unset($params[$name]);
- if (isset($this->_paramRules[$name])) {
- $tr["<$name>"] = '';
- }
- } elseif (!isset($this->_paramRules[$name])) {
- return false;
- }
- }
+ // match the route part first
+ if ($route !== $this->route) {
+ if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
+ foreach ($this->_routeParams as $name => $token) {
+ if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
+ $tr[$token] = '';
+ } else {
+ $tr[$token] = $matches[$name];
+ }
+ }
+ } else {
+ return false;
+ }
+ }
- // match params in the pattern
- foreach ($this->_paramRules as $name => $rule) {
- if (isset($params[$name]) && !is_array($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
- $tr["<$name>"] = urlencode($params[$name]);
- unset($params[$name]);
- } elseif (!isset($this->defaults[$name]) || isset($params[$name])) {
- return false;
- }
- }
+ // match default params
+ // if a default param is not in the route pattern, its value must also be matched
+ foreach ($this->defaults as $name => $value) {
+ if (isset($this->_routeParams[$name])) {
+ continue;
+ }
+ if (!isset($params[$name])) {
+ return false;
+ } elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
+ unset($params[$name]);
+ if (isset($this->_paramRules[$name])) {
+ $tr["<$name>"] = '';
+ }
+ } elseif (!isset($this->_paramRules[$name])) {
+ return false;
+ }
+ }
- $url = trim(strtr($this->_template, $tr), '/');
- if ($this->host !== null) {
- $pos = strpos($url, '/', 8);
- if ($pos !== false) {
- $url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos));
- }
- } elseif (strpos($url, '//') !== false) {
- $url = preg_replace('#/+#', '/', $url);
- }
+ // match params in the pattern
+ foreach ($this->_paramRules as $name => $rule) {
+ if (isset($params[$name]) && !is_array($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
+ $tr["<$name>"] = urlencode($params[$name]);
+ unset($params[$name]);
+ } elseif (!isset($this->defaults[$name]) || isset($params[$name])) {
+ return false;
+ }
+ }
- if ($url !== '') {
- $url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
- }
+ $url = trim(strtr($this->_template, $tr), '/');
+ if ($this->host !== null) {
+ $pos = strpos($url, '/', 8);
+ if ($pos !== false) {
+ $url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos));
+ }
+ } elseif (strpos($url, '//') !== false) {
+ $url = preg_replace('#/+#', '/', $url);
+ }
- if (!empty($params) && ($query = http_build_query($params)) !== '') {
- $url .= '?' . $query;
- }
- return $url;
- }
+ if ($url !== '') {
+ $url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
+ }
+
+ if (!empty($params) && ($query = http_build_query($params)) !== '') {
+ $url .= '?' . $query;
+ }
+
+ return $url;
+ }
}
diff --git a/framework/web/UrlRuleInterface.php b/framework/web/UrlRuleInterface.php
index e6a53859604..9063c7732a9 100644
--- a/framework/web/UrlRuleInterface.php
+++ b/framework/web/UrlRuleInterface.php
@@ -15,20 +15,20 @@
*/
interface UrlRuleInterface
{
- /**
- * Parses the given request and returns the corresponding route and parameters.
- * @param UrlManager $manager the URL manager
- * @param Request $request the request component
- * @return array|boolean the parsing result. The route and the parameters are returned as an array.
- * If false, it means this rule cannot be used to parse this path info.
- */
- public function parseRequest($manager, $request);
- /**
- * Creates a URL according to the given route and parameters.
- * @param UrlManager $manager the URL manager
- * @param string $route the route. It should not have slashes at the beginning or the end.
- * @param array $params the parameters
- * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL.
- */
- public function createUrl($manager, $route, $params);
+ /**
+ * Parses the given request and returns the corresponding route and parameters.
+ * @param UrlManager $manager the URL manager
+ * @param Request $request the request component
+ * @return array|boolean the parsing result. The route and the parameters are returned as an array.
+ * If false, it means this rule cannot be used to parse this path info.
+ */
+ public function parseRequest($manager, $request);
+ /**
+ * Creates a URL according to the given route and parameters.
+ * @param UrlManager $manager the URL manager
+ * @param string $route the route. It should not have slashes at the beginning or the end.
+ * @param array $params the parameters
+ * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL.
+ */
+ public function createUrl($manager, $route, $params);
}
diff --git a/framework/web/User.php b/framework/web/User.php
index cc0e5a7facd..0f51f57470f 100644
--- a/framework/web/User.php
+++ b/framework/web/User.php
@@ -57,532 +57,540 @@
*/
class User extends Component
{
- const EVENT_BEFORE_LOGIN = 'beforeLogin';
- const EVENT_AFTER_LOGIN = 'afterLogin';
- const EVENT_BEFORE_LOGOUT = 'beforeLogout';
- const EVENT_AFTER_LOGOUT = 'afterLogout';
-
- /**
- * @var string the class name of the [[identity]] object.
- */
- public $identityClass;
- /**
- * @var boolean whether to enable cookie-based login. Defaults to false.
- */
- public $enableAutoLogin = false;
- /**
- * @var string|array the URL for login when [[loginRequired()]] is called.
- * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
- * The first element of the array should be the route to the login action, and the rest of
- * the name-value pairs are GET parameters used to construct the login URL. For example,
- *
- * ~~~
- * ['site/login', 'ref' => 1]
- * ~~~
- *
- * If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called.
- */
- public $loginUrl = ['site/login'];
- /**
- * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
- * @see Cookie
- */
- public $identityCookie = ['name' => '_identity', 'httpOnly' => true];
- /**
- * @var integer the number of seconds in which the user will be logged out automatically if he
- * remains inactive. If this property is not set, the user will be logged out after
- * the current session expires (c.f. [[Session::timeout]]).
- */
- public $authTimeout;
- /**
- * @var boolean whether to automatically renew the identity cookie each time a page is requested.
- * This property is effective only when [[enableAutoLogin]] is true.
- * When this is false, the identity cookie will expire after the specified duration since the user
- * is initially logged in. When this is true, the identity cookie will expire after the specified duration
- * since the user visits the site the last time.
- * @see enableAutoLogin
- */
- public $autoRenewCookie = true;
- /**
- * @var string the session variable name used to store the value of [[id]].
- */
- public $idParam = '__id';
- /**
- * @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
- * This is used when [[authTimeout]] is set.
- */
- public $authTimeoutParam = '__expire';
- /**
- * @var string the session variable name used to store the value of [[returnUrl]].
- */
- public $returnUrlParam = '__returnUrl';
-
- private $_access = [];
-
-
- /**
- * Initializes the application component.
- */
- public function init()
- {
- parent::init();
-
- if ($this->identityClass === null) {
- throw new InvalidConfigException('User::identityClass must be set.');
- }
- if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) {
- throw new InvalidConfigException('User::identityCookie must contain the "name" element.');
- }
- }
-
- private $_identity = false;
-
- /**
- * Returns the identity object associated with the currently logged-in user.
- * @param boolean $checkSession whether to check the session if the identity has never been determined before.
- * If the identity is already determined (e.g., by calling [[setIdentity()]] or [[login()]]),
- * then this parameter has no effect.
- * @return IdentityInterface the identity object associated with the currently logged-in user.
- * Null is returned if the user is not logged in (not authenticated).
- * @see login()
- * @see logout()
- */
- public function getIdentity($checkSession = true)
- {
- if ($this->_identity === false) {
- if ($checkSession) {
- $this->renewAuthStatus();
- } else {
- return null;
- }
- }
- return $this->_identity;
- }
-
- /**
- * Sets the user identity object.
- *
- * This method does nothing else except storing the specified identity object in the internal variable.
- * For this reason, this method is best used when the user authentication status should not be maintained
- * by session.
- *
- * This method is also called by other more sophisticated methods, such as [[login()]], [[logout()]],
- * [[switchIdentity()]]. Those methods will try to use session and cookie to maintain the user authentication
- * status.
- *
- * @param IdentityInterface $identity the identity object associated with the currently logged user.
- */
- public function setIdentity($identity)
- {
- $this->_identity = $identity;
- $this->_access = [];
- }
-
- /**
- * Logs in a user.
- *
- * By logging in a user, you may obtain the user identity information each time through [[identity]].
- *
- * The login status is maintained according to the `$duration` parameter:
- *
- * - `$duration == 0`: the identity information will be stored in session and will be available
- * via [[identity]] as long as the session remains active.
- * - `$duration > 0`: the identity information will be stored in session. If [[enableAutoLogin]] is true,
- * it will also be stored in a cookie which will expire in `$duration` seconds. As long as
- * the cookie remains valid or the session is active, you may obtain the user identity information
- * via [[identity]].
- *
- * @param IdentityInterface $identity the user identity (which should already be authenticated)
- * @param integer $duration number of seconds that the user can remain in logged-in status.
- * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed.
- * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported.
- * @return boolean whether the user is logged in
- */
- public function login($identity, $duration = 0)
- {
- if ($this->beforeLogin($identity, false, $duration)) {
- $this->switchIdentity($identity, $duration);
- $id = $identity->getId();
- $ip = Yii::$app->getRequest()->getUserIP();
- Yii::info("User '$id' logged in from $ip with duration $duration.", __METHOD__);
- $this->afterLogin($identity, false, $duration);
- }
- return !$this->getIsGuest();
- }
-
- /**
- * Logs in a user by the given access token.
- * Note that unlike [[login()]], this method will NOT start a session to remember the user authentication status.
- * Also if the access token is invalid, the user will remain as a guest.
- * @param string $token the access token
- * @return IdentityInterface the identity associated with the given access token. Null is returned if
- * the access token is invalid.
- */
- public function loginByAccessToken($token)
- {
- /** @var IdentityInterface $class */
- $class = $this->identityClass;
- $identity = $class::findIdentityByAccessToken($token);
- $this->setIdentity($identity);
- return $identity;
- }
-
- /**
- * Logs in a user by cookie.
- *
- * This method attempts to log in a user using the ID and authKey information
- * provided by the given cookie.
- */
- protected function loginByCookie()
- {
- $name = $this->identityCookie['name'];
- $value = Yii::$app->getRequest()->getCookies()->getValue($name);
- if ($value !== null) {
- $data = json_decode($value, true);
- if (count($data) === 3 && isset($data[0], $data[1], $data[2])) {
- list ($id, $authKey, $duration) = $data;
- /** @var IdentityInterface $class */
- $class = $this->identityClass;
- $identity = $class::findIdentity($id);
- if ($identity !== null && $identity->validateAuthKey($authKey)) {
- if ($this->beforeLogin($identity, true, $duration)) {
- $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
- $ip = Yii::$app->getRequest()->getUserIP();
- Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
- $this->afterLogin($identity, true, $duration);
- }
- } elseif ($identity !== null) {
- Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
- }
- }
- }
- }
-
- /**
- * Logs out the current user.
- * This will remove authentication-related session data.
- * If `$destroySession` is true, all session data will be removed.
- * @param boolean $destroySession whether to destroy the whole session. Defaults to true.
- */
- public function logout($destroySession = true)
- {
- $identity = $this->getIdentity();
- if ($identity !== null && $this->beforeLogout($identity)) {
- $this->switchIdentity(null);
- $id = $identity->getId();
- $ip = Yii::$app->getRequest()->getUserIP();
- Yii::info("User '$id' logged out from $ip.", __METHOD__);
- if ($destroySession) {
- Yii::$app->getSession()->destroy();
- }
- $this->afterLogout($identity);
- }
- }
-
- /**
- * Returns a value indicating whether the user is a guest (not authenticated).
- * @return boolean whether the current user is a guest.
- */
- public function getIsGuest()
- {
- return $this->getIdentity() === null;
- }
-
- /**
- * Returns a value that uniquely represents the user.
- * @return string|integer the unique identifier for the user. If null, it means the user is a guest.
- */
- public function getId()
- {
- $identity = $this->getIdentity();
- return $identity !== null ? $identity->getId() : null;
- }
-
- /**
- * Returns the URL that the user should be redirected to after successful login.
- * This property is usually used by the login action. If the login is successful,
- * the action should read this property and use it to redirect the user browser.
- * @param string|array $defaultUrl the default return URL in case it was not set previously.
- * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
- * Please refer to [[setReturnUrl()]] on accepted format of the URL.
- * @return string the URL that the user should be redirected to after login.
- * @see loginRequired()
- */
- public function getReturnUrl($defaultUrl = null)
- {
- $url = Yii::$app->getSession()->get($this->returnUrlParam, $defaultUrl);
- if (is_array($url)) {
- if (isset($url[0])) {
- $route = array_shift($url);
- return Yii::$app->getUrlManager()->createUrl($route, $url);
- } else {
- $url = null;
- }
- }
- return $url === null ? Yii::$app->getHomeUrl() : $url;
- }
-
- /**
- * @param string|array $url the URL that the user should be redirected to after login.
- * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
- * The first element of the array should be the route, and the rest of
- * the name-value pairs are GET parameters used to construct the URL. For example,
- *
- * ~~~
- * ['admin/index', 'ref' => 1]
- * ~~~
- */
- public function setReturnUrl($url)
- {
- Yii::$app->getSession()->set($this->returnUrlParam, $url);
- }
-
- /**
- * Redirects the user browser to the login page.
- * Before the redirection, the current URL (if it's not an AJAX url) will be
- * kept as [[returnUrl]] so that the user browser may be redirected back
- * to the current page after successful login. Make sure you set [[loginUrl]]
- * so that the user browser can be redirected to the specified login URL after
- * calling this method.
- *
- * Note that when [[loginUrl]] is set, calling this method will NOT terminate the application execution.
- *
- * @return Response the redirection response if [[loginUrl]] is set
- * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set
- */
- public function loginRequired()
- {
- $request = Yii::$app->getRequest();
- if (!$request->getIsAjax()) {
- $this->setReturnUrl($request->getUrl());
- }
- if ($this->loginUrl !== null) {
- return Yii::$app->getResponse()->redirect($this->loginUrl);
- } else {
- throw new ForbiddenHttpException(Yii::t('yii', 'Login Required'));
- }
- }
-
- /**
- * This method is called before logging in a user.
- * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event.
- * If you override this method, make sure you call the parent implementation
- * so that the event is triggered.
- * @param IdentityInterface $identity the user identity information
- * @param boolean $cookieBased whether the login is cookie-based
- * @param integer $duration number of seconds that the user can remain in logged-in status.
- * If 0, it means login till the user closes the browser or the session is manually destroyed.
- * @return boolean whether the user should continue to be logged in
- */
- protected function beforeLogin($identity, $cookieBased, $duration)
- {
- $event = new UserEvent([
- 'identity' => $identity,
- 'cookieBased' => $cookieBased,
- 'duration' => $duration,
- ]);
- $this->trigger(self::EVENT_BEFORE_LOGIN, $event);
- return $event->isValid;
- }
-
- /**
- * This method is called after the user is successfully logged in.
- * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event.
- * If you override this method, make sure you call the parent implementation
- * so that the event is triggered.
- * @param IdentityInterface $identity the user identity information
- * @param boolean $cookieBased whether the login is cookie-based
- * @param integer $duration number of seconds that the user can remain in logged-in status.
- * If 0, it means login till the user closes the browser or the session is manually destroyed.
- */
- protected function afterLogin($identity, $cookieBased, $duration)
- {
- $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([
- 'identity' => $identity,
- 'cookieBased' => $cookieBased,
- 'duration' => $duration,
- ]));
- }
-
- /**
- * This method is invoked when calling [[logout()]] to log out a user.
- * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event.
- * If you override this method, make sure you call the parent implementation
- * so that the event is triggered.
- * @param IdentityInterface $identity the user identity information
- * @return boolean whether the user should continue to be logged out
- */
- protected function beforeLogout($identity)
- {
- $event = new UserEvent([
- 'identity' => $identity,
- ]);
- $this->trigger(self::EVENT_BEFORE_LOGOUT, $event);
- return $event->isValid;
- }
-
- /**
- * This method is invoked right after a user is logged out via [[logout()]].
- * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event.
- * If you override this method, make sure you call the parent implementation
- * so that the event is triggered.
- * @param IdentityInterface $identity the user identity information
- */
- protected function afterLogout($identity)
- {
- $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([
- 'identity' => $identity,
- ]));
- }
-
- /**
- * Renews the identity cookie.
- * This method will set the expiration time of the identity cookie to be the current time
- * plus the originally specified cookie duration.
- */
- protected function renewIdentityCookie()
- {
- $name = $this->identityCookie['name'];
- $value = Yii::$app->getRequest()->getCookies()->getValue($name);
- if ($value !== null) {
- $data = json_decode($value, true);
- if (is_array($data) && isset($data[2])) {
- $cookie = new Cookie($this->identityCookie);
- $cookie->value = $value;
- $cookie->expire = time() + (int)$data[2];
- Yii::$app->getResponse()->getCookies()->add($cookie);
- }
- }
- }
-
- /**
- * Sends an identity cookie.
- * This method is used when [[enableAutoLogin]] is true.
- * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login
- * information in the cookie.
- * @param IdentityInterface $identity
- * @param integer $duration number of seconds that the user can remain in logged-in status.
- * @see loginByCookie()
- */
- protected function sendIdentityCookie($identity, $duration)
- {
- $cookie = new Cookie($this->identityCookie);
- $cookie->value = json_encode([
- $identity->getId(),
- $identity->getAuthKey(),
- $duration,
- ]);
- $cookie->expire = time() + $duration;
- Yii::$app->getResponse()->getCookies()->add($cookie);
- }
-
- /**
- * Switches to a new identity for the current user.
- *
- * This method may use session and/or cookie to store the user identity information,
- * according to the value of `$duration`. Please refer to [[login()]] for more details.
- *
- * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]]
- * when the current user needs to be associated with the corresponding identity information.
- *
- * @param IdentityInterface $identity the identity information to be associated with the current user.
- * If null, it means switching the current user to be a guest.
- * @param integer $duration number of seconds that the user can remain in logged-in status.
- * This parameter is used only when `$identity` is not null.
- */
- public function switchIdentity($identity, $duration = 0)
- {
- $session = Yii::$app->getSession();
- if (!YII_ENV_TEST) {
- $session->regenerateID(true);
- }
- $this->setIdentity($identity);
- $session->remove($this->idParam);
- $session->remove($this->authTimeoutParam);
- if ($identity instanceof IdentityInterface) {
- $session->set($this->idParam, $identity->getId());
- if ($this->authTimeout !== null) {
- $session->set($this->authTimeoutParam, time() + $this->authTimeout);
- }
- if ($duration > 0 && $this->enableAutoLogin) {
- $this->sendIdentityCookie($identity, $duration);
- }
- } elseif ($this->enableAutoLogin) {
- Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie));
- }
- }
-
- /**
- * Updates the authentication status using the information from session and cookie.
- *
- * This method will try to determine the user identity using the [[idParam]] session variable.
- *
- * If [[authTimeout]] is set, this method will refresh the timer.
- *
- * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]]
- * if [[enableAutoLogin]] is true.
- */
- protected function renewAuthStatus()
- {
- $session = Yii::$app->getSession();
- $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;
-
- if ($id === null) {
- $identity = null;
- } else {
- /** @var IdentityInterface $class */
- $class = $this->identityClass;
- $identity = $class::findIdentity($id);
- }
-
- $this->setIdentity($identity);
-
- if ($this->authTimeout !== null && $identity !== null) {
- $expire = $session->get($this->authTimeoutParam);
- if ($expire !== null && $expire < time()) {
- $this->logout(false);
- } else {
- $session->set($this->authTimeoutParam, time() + $this->authTimeout);
- }
- }
-
- if ($this->enableAutoLogin) {
- if ($this->getIsGuest()) {
- $this->loginByCookie();
- } elseif ($this->autoRenewCookie) {
- $this->renewIdentityCookie();
- }
- }
- }
-
- /**
- * Performs access check for this user.
- * @param string $operation the name of the operation that need access check.
- * @param array $params name-value pairs that would be passed to business rules associated
- * with the tasks and roles assigned to the user. A param with name 'userId' is added to
- * this array, which holds the value of [[id]] when [[DbAuthManager]] or
- * [[PhpAuthManager]] is used.
- * @param boolean $allowCaching whether to allow caching the result of access check.
- * When this parameter is true (default), if the access check of an operation was performed
- * before, its result will be directly returned when calling this method to check the same
- * operation. If this parameter is false, this method will always call
- * [[AuthManager::checkAccess()]] to obtain the up-to-date access result. Note that this
- * caching is effective only within the same request and only works when `$params = []`.
- * @return boolean whether the operations can be performed by this user.
- */
- public function checkAccess($operation, $params = [], $allowCaching = true)
- {
- $auth = Yii::$app->getAuthManager();
- if ($auth === null) {
- return false;
- }
- if ($allowCaching && empty($params) && isset($this->_access[$operation])) {
- return $this->_access[$operation];
- }
- $access = $auth->checkAccess($this->getId(), $operation, $params);
- if ($allowCaching && empty($params)) {
- $this->_access[$operation] = $access;
- }
- return $access;
- }
+ const EVENT_BEFORE_LOGIN = 'beforeLogin';
+ const EVENT_AFTER_LOGIN = 'afterLogin';
+ const EVENT_BEFORE_LOGOUT = 'beforeLogout';
+ const EVENT_AFTER_LOGOUT = 'afterLogout';
+
+ /**
+ * @var string the class name of the [[identity]] object.
+ */
+ public $identityClass;
+ /**
+ * @var boolean whether to enable cookie-based login. Defaults to false.
+ */
+ public $enableAutoLogin = false;
+ /**
+ * @var string|array the URL for login when [[loginRequired()]] is called.
+ * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
+ * The first element of the array should be the route to the login action, and the rest of
+ * the name-value pairs are GET parameters used to construct the login URL. For example,
+ *
+ * ~~~
+ * ['site/login', 'ref' => 1]
+ * ~~~
+ *
+ * If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called.
+ */
+ public $loginUrl = ['site/login'];
+ /**
+ * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
+ * @see Cookie
+ */
+ public $identityCookie = ['name' => '_identity', 'httpOnly' => true];
+ /**
+ * @var integer the number of seconds in which the user will be logged out automatically if he
+ * remains inactive. If this property is not set, the user will be logged out after
+ * the current session expires (c.f. [[Session::timeout]]).
+ */
+ public $authTimeout;
+ /**
+ * @var boolean whether to automatically renew the identity cookie each time a page is requested.
+ * This property is effective only when [[enableAutoLogin]] is true.
+ * When this is false, the identity cookie will expire after the specified duration since the user
+ * is initially logged in. When this is true, the identity cookie will expire after the specified duration
+ * since the user visits the site the last time.
+ * @see enableAutoLogin
+ */
+ public $autoRenewCookie = true;
+ /**
+ * @var string the session variable name used to store the value of [[id]].
+ */
+ public $idParam = '__id';
+ /**
+ * @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
+ * This is used when [[authTimeout]] is set.
+ */
+ public $authTimeoutParam = '__expire';
+ /**
+ * @var string the session variable name used to store the value of [[returnUrl]].
+ */
+ public $returnUrlParam = '__returnUrl';
+
+ private $_access = [];
+
+ /**
+ * Initializes the application component.
+ */
+ public function init()
+ {
+ parent::init();
+
+ if ($this->identityClass === null) {
+ throw new InvalidConfigException('User::identityClass must be set.');
+ }
+ if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) {
+ throw new InvalidConfigException('User::identityCookie must contain the "name" element.');
+ }
+ }
+
+ private $_identity = false;
+
+ /**
+ * Returns the identity object associated with the currently logged-in user.
+ * @param boolean $checkSession whether to check the session if the identity has never been determined before.
+ * If the identity is already determined (e.g., by calling [[setIdentity()]] or [[login()]]),
+ * then this parameter has no effect.
+ * @return IdentityInterface the identity object associated with the currently logged-in user.
+ * Null is returned if the user is not logged in (not authenticated).
+ * @see login()
+ * @see logout()
+ */
+ public function getIdentity($checkSession = true)
+ {
+ if ($this->_identity === false) {
+ if ($checkSession) {
+ $this->renewAuthStatus();
+ } else {
+ return null;
+ }
+ }
+
+ return $this->_identity;
+ }
+
+ /**
+ * Sets the user identity object.
+ *
+ * This method does nothing else except storing the specified identity object in the internal variable.
+ * For this reason, this method is best used when the user authentication status should not be maintained
+ * by session.
+ *
+ * This method is also called by other more sophisticated methods, such as [[login()]], [[logout()]],
+ * [[switchIdentity()]]. Those methods will try to use session and cookie to maintain the user authentication
+ * status.
+ *
+ * @param IdentityInterface $identity the identity object associated with the currently logged user.
+ */
+ public function setIdentity($identity)
+ {
+ $this->_identity = $identity;
+ $this->_access = [];
+ }
+
+ /**
+ * Logs in a user.
+ *
+ * By logging in a user, you may obtain the user identity information each time through [[identity]].
+ *
+ * The login status is maintained according to the `$duration` parameter:
+ *
+ * - `$duration == 0`: the identity information will be stored in session and will be available
+ * via [[identity]] as long as the session remains active.
+ * - `$duration > 0`: the identity information will be stored in session. If [[enableAutoLogin]] is true,
+ * it will also be stored in a cookie which will expire in `$duration` seconds. As long as
+ * the cookie remains valid or the session is active, you may obtain the user identity information
+ * via [[identity]].
+ *
+ * @param IdentityInterface $identity the user identity (which should already be authenticated)
+ * @param integer $duration number of seconds that the user can remain in logged-in status.
+ * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed.
+ * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported.
+ * @return boolean whether the user is logged in
+ */
+ public function login($identity, $duration = 0)
+ {
+ if ($this->beforeLogin($identity, false, $duration)) {
+ $this->switchIdentity($identity, $duration);
+ $id = $identity->getId();
+ $ip = Yii::$app->getRequest()->getUserIP();
+ Yii::info("User '$id' logged in from $ip with duration $duration.", __METHOD__);
+ $this->afterLogin($identity, false, $duration);
+ }
+
+ return !$this->getIsGuest();
+ }
+
+ /**
+ * Logs in a user by the given access token.
+ * Note that unlike [[login()]], this method will NOT start a session to remember the user authentication status.
+ * Also if the access token is invalid, the user will remain as a guest.
+ * @param string $token the access token
+ * @return IdentityInterface the identity associated with the given access token. Null is returned if
+ * the access token is invalid.
+ */
+ public function loginByAccessToken($token)
+ {
+ /** @var IdentityInterface $class */
+ $class = $this->identityClass;
+ $identity = $class::findIdentityByAccessToken($token);
+ $this->setIdentity($identity);
+
+ return $identity;
+ }
+
+ /**
+ * Logs in a user by cookie.
+ *
+ * This method attempts to log in a user using the ID and authKey information
+ * provided by the given cookie.
+ */
+ protected function loginByCookie()
+ {
+ $name = $this->identityCookie['name'];
+ $value = Yii::$app->getRequest()->getCookies()->getValue($name);
+ if ($value !== null) {
+ $data = json_decode($value, true);
+ if (count($data) === 3 && isset($data[0], $data[1], $data[2])) {
+ list ($id, $authKey, $duration) = $data;
+ /** @var IdentityInterface $class */
+ $class = $this->identityClass;
+ $identity = $class::findIdentity($id);
+ if ($identity !== null && $identity->validateAuthKey($authKey)) {
+ if ($this->beforeLogin($identity, true, $duration)) {
+ $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
+ $ip = Yii::$app->getRequest()->getUserIP();
+ Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
+ $this->afterLogin($identity, true, $duration);
+ }
+ } elseif ($identity !== null) {
+ Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
+ }
+ }
+ }
+ }
+
+ /**
+ * Logs out the current user.
+ * This will remove authentication-related session data.
+ * If `$destroySession` is true, all session data will be removed.
+ * @param boolean $destroySession whether to destroy the whole session. Defaults to true.
+ */
+ public function logout($destroySession = true)
+ {
+ $identity = $this->getIdentity();
+ if ($identity !== null && $this->beforeLogout($identity)) {
+ $this->switchIdentity(null);
+ $id = $identity->getId();
+ $ip = Yii::$app->getRequest()->getUserIP();
+ Yii::info("User '$id' logged out from $ip.", __METHOD__);
+ if ($destroySession) {
+ Yii::$app->getSession()->destroy();
+ }
+ $this->afterLogout($identity);
+ }
+ }
+
+ /**
+ * Returns a value indicating whether the user is a guest (not authenticated).
+ * @return boolean whether the current user is a guest.
+ */
+ public function getIsGuest()
+ {
+ return $this->getIdentity() === null;
+ }
+
+ /**
+ * Returns a value that uniquely represents the user.
+ * @return string|integer the unique identifier for the user. If null, it means the user is a guest.
+ */
+ public function getId()
+ {
+ $identity = $this->getIdentity();
+
+ return $identity !== null ? $identity->getId() : null;
+ }
+
+ /**
+ * Returns the URL that the user should be redirected to after successful login.
+ * This property is usually used by the login action. If the login is successful,
+ * the action should read this property and use it to redirect the user browser.
+ * @param string|array $defaultUrl the default return URL in case it was not set previously.
+ * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
+ * Please refer to [[setReturnUrl()]] on accepted format of the URL.
+ * @return string the URL that the user should be redirected to after login.
+ * @see loginRequired()
+ */
+ public function getReturnUrl($defaultUrl = null)
+ {
+ $url = Yii::$app->getSession()->get($this->returnUrlParam, $defaultUrl);
+ if (is_array($url)) {
+ if (isset($url[0])) {
+ $route = array_shift($url);
+
+ return Yii::$app->getUrlManager()->createUrl($route, $url);
+ } else {
+ $url = null;
+ }
+ }
+
+ return $url === null ? Yii::$app->getHomeUrl() : $url;
+ }
+
+ /**
+ * @param string|array $url the URL that the user should be redirected to after login.
+ * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
+ * The first element of the array should be the route, and the rest of
+ * the name-value pairs are GET parameters used to construct the URL. For example,
+ *
+ * ~~~
+ * ['admin/index', 'ref' => 1]
+ * ~~~
+ */
+ public function setReturnUrl($url)
+ {
+ Yii::$app->getSession()->set($this->returnUrlParam, $url);
+ }
+
+ /**
+ * Redirects the user browser to the login page.
+ * Before the redirection, the current URL (if it's not an AJAX url) will be
+ * kept as [[returnUrl]] so that the user browser may be redirected back
+ * to the current page after successful login. Make sure you set [[loginUrl]]
+ * so that the user browser can be redirected to the specified login URL after
+ * calling this method.
+ *
+ * Note that when [[loginUrl]] is set, calling this method will NOT terminate the application execution.
+ *
+ * @return Response the redirection response if [[loginUrl]] is set
+ * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set
+ */
+ public function loginRequired()
+ {
+ $request = Yii::$app->getRequest();
+ if (!$request->getIsAjax()) {
+ $this->setReturnUrl($request->getUrl());
+ }
+ if ($this->loginUrl !== null) {
+ return Yii::$app->getResponse()->redirect($this->loginUrl);
+ } else {
+ throw new ForbiddenHttpException(Yii::t('yii', 'Login Required'));
+ }
+ }
+
+ /**
+ * This method is called before logging in a user.
+ * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event.
+ * If you override this method, make sure you call the parent implementation
+ * so that the event is triggered.
+ * @param IdentityInterface $identity the user identity information
+ * @param boolean $cookieBased whether the login is cookie-based
+ * @param integer $duration number of seconds that the user can remain in logged-in status.
+ * If 0, it means login till the user closes the browser or the session is manually destroyed.
+ * @return boolean whether the user should continue to be logged in
+ */
+ protected function beforeLogin($identity, $cookieBased, $duration)
+ {
+ $event = new UserEvent([
+ 'identity' => $identity,
+ 'cookieBased' => $cookieBased,
+ 'duration' => $duration,
+ ]);
+ $this->trigger(self::EVENT_BEFORE_LOGIN, $event);
+
+ return $event->isValid;
+ }
+
+ /**
+ * This method is called after the user is successfully logged in.
+ * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event.
+ * If you override this method, make sure you call the parent implementation
+ * so that the event is triggered.
+ * @param IdentityInterface $identity the user identity information
+ * @param boolean $cookieBased whether the login is cookie-based
+ * @param integer $duration number of seconds that the user can remain in logged-in status.
+ * If 0, it means login till the user closes the browser or the session is manually destroyed.
+ */
+ protected function afterLogin($identity, $cookieBased, $duration)
+ {
+ $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([
+ 'identity' => $identity,
+ 'cookieBased' => $cookieBased,
+ 'duration' => $duration,
+ ]));
+ }
+
+ /**
+ * This method is invoked when calling [[logout()]] to log out a user.
+ * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event.
+ * If you override this method, make sure you call the parent implementation
+ * so that the event is triggered.
+ * @param IdentityInterface $identity the user identity information
+ * @return boolean whether the user should continue to be logged out
+ */
+ protected function beforeLogout($identity)
+ {
+ $event = new UserEvent([
+ 'identity' => $identity,
+ ]);
+ $this->trigger(self::EVENT_BEFORE_LOGOUT, $event);
+
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked right after a user is logged out via [[logout()]].
+ * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event.
+ * If you override this method, make sure you call the parent implementation
+ * so that the event is triggered.
+ * @param IdentityInterface $identity the user identity information
+ */
+ protected function afterLogout($identity)
+ {
+ $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([
+ 'identity' => $identity,
+ ]));
+ }
+
+ /**
+ * Renews the identity cookie.
+ * This method will set the expiration time of the identity cookie to be the current time
+ * plus the originally specified cookie duration.
+ */
+ protected function renewIdentityCookie()
+ {
+ $name = $this->identityCookie['name'];
+ $value = Yii::$app->getRequest()->getCookies()->getValue($name);
+ if ($value !== null) {
+ $data = json_decode($value, true);
+ if (is_array($data) && isset($data[2])) {
+ $cookie = new Cookie($this->identityCookie);
+ $cookie->value = $value;
+ $cookie->expire = time() + (int) $data[2];
+ Yii::$app->getResponse()->getCookies()->add($cookie);
+ }
+ }
+ }
+
+ /**
+ * Sends an identity cookie.
+ * This method is used when [[enableAutoLogin]] is true.
+ * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login
+ * information in the cookie.
+ * @param IdentityInterface $identity
+ * @param integer $duration number of seconds that the user can remain in logged-in status.
+ * @see loginByCookie()
+ */
+ protected function sendIdentityCookie($identity, $duration)
+ {
+ $cookie = new Cookie($this->identityCookie);
+ $cookie->value = json_encode([
+ $identity->getId(),
+ $identity->getAuthKey(),
+ $duration,
+ ]);
+ $cookie->expire = time() + $duration;
+ Yii::$app->getResponse()->getCookies()->add($cookie);
+ }
+
+ /**
+ * Switches to a new identity for the current user.
+ *
+ * This method may use session and/or cookie to store the user identity information,
+ * according to the value of `$duration`. Please refer to [[login()]] for more details.
+ *
+ * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]]
+ * when the current user needs to be associated with the corresponding identity information.
+ *
+ * @param IdentityInterface $identity the identity information to be associated with the current user.
+ * If null, it means switching the current user to be a guest.
+ * @param integer $duration number of seconds that the user can remain in logged-in status.
+ * This parameter is used only when `$identity` is not null.
+ */
+ public function switchIdentity($identity, $duration = 0)
+ {
+ $session = Yii::$app->getSession();
+ if (!YII_ENV_TEST) {
+ $session->regenerateID(true);
+ }
+ $this->setIdentity($identity);
+ $session->remove($this->idParam);
+ $session->remove($this->authTimeoutParam);
+ if ($identity instanceof IdentityInterface) {
+ $session->set($this->idParam, $identity->getId());
+ if ($this->authTimeout !== null) {
+ $session->set($this->authTimeoutParam, time() + $this->authTimeout);
+ }
+ if ($duration > 0 && $this->enableAutoLogin) {
+ $this->sendIdentityCookie($identity, $duration);
+ }
+ } elseif ($this->enableAutoLogin) {
+ Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie));
+ }
+ }
+
+ /**
+ * Updates the authentication status using the information from session and cookie.
+ *
+ * This method will try to determine the user identity using the [[idParam]] session variable.
+ *
+ * If [[authTimeout]] is set, this method will refresh the timer.
+ *
+ * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]]
+ * if [[enableAutoLogin]] is true.
+ */
+ protected function renewAuthStatus()
+ {
+ $session = Yii::$app->getSession();
+ $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;
+
+ if ($id === null) {
+ $identity = null;
+ } else {
+ /** @var IdentityInterface $class */
+ $class = $this->identityClass;
+ $identity = $class::findIdentity($id);
+ }
+
+ $this->setIdentity($identity);
+
+ if ($this->authTimeout !== null && $identity !== null) {
+ $expire = $session->get($this->authTimeoutParam);
+ if ($expire !== null && $expire < time()) {
+ $this->logout(false);
+ } else {
+ $session->set($this->authTimeoutParam, time() + $this->authTimeout);
+ }
+ }
+
+ if ($this->enableAutoLogin) {
+ if ($this->getIsGuest()) {
+ $this->loginByCookie();
+ } elseif ($this->autoRenewCookie) {
+ $this->renewIdentityCookie();
+ }
+ }
+ }
+
+ /**
+ * Performs access check for this user.
+ * @param string $operation the name of the operation that need access check.
+ * @param array $params name-value pairs that would be passed to business rules associated
+ * with the tasks and roles assigned to the user. A param with name 'userId' is added to
+ * this array, which holds the value of [[id]] when [[DbAuthManager]] or
+ * [[PhpAuthManager]] is used.
+ * @param boolean $allowCaching whether to allow caching the result of access check.
+ * When this parameter is true (default), if the access check of an operation was performed
+ * before, its result will be directly returned when calling this method to check the same
+ * operation. If this parameter is false, this method will always call
+ * [[AuthManager::checkAccess()]] to obtain the up-to-date access result. Note that this
+ * caching is effective only within the same request and only works when `$params = []`.
+ * @return boolean whether the operations can be performed by this user.
+ */
+ public function checkAccess($operation, $params = [], $allowCaching = true)
+ {
+ $auth = Yii::$app->getAuthManager();
+ if ($auth === null) {
+ return false;
+ }
+ if ($allowCaching && empty($params) && isset($this->_access[$operation])) {
+ return $this->_access[$operation];
+ }
+ $access = $auth->checkAccess($this->getId(), $operation, $params);
+ if ($allowCaching && empty($params)) {
+ $this->_access[$operation] = $access;
+ }
+
+ return $access;
+ }
}
diff --git a/framework/web/UserEvent.php b/framework/web/UserEvent.php
index bc6d5fe0664..cc2e328b6e5 100644
--- a/framework/web/UserEvent.php
+++ b/framework/web/UserEvent.php
@@ -17,24 +17,24 @@
*/
class UserEvent extends Event
{
- /**
- * @var IdentityInterface the identity object associated with this event
- */
- public $identity;
- /**
- * @var boolean whether the login is cookie-based. This property is only meaningful
- * for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_AFTER_LOGIN]] events.
- */
- public $cookieBased;
- /**
- * @var integer $duration number of seconds that the user can remain in logged-in status.
- * If 0, it means login till the user closes the browser or the session is manually destroyed.
- */
- public $duration;
- /**
- * @var boolean whether the login or logout should proceed.
- * Event handlers may modify this property to determine whether the login or logout should proceed.
- * This property is only meaningful for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_BEFORE_LOGOUT]] events.
- */
- public $isValid = true;
+ /**
+ * @var IdentityInterface the identity object associated with this event
+ */
+ public $identity;
+ /**
+ * @var boolean whether the login is cookie-based. This property is only meaningful
+ * for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_AFTER_LOGIN]] events.
+ */
+ public $cookieBased;
+ /**
+ * @var integer $duration number of seconds that the user can remain in logged-in status.
+ * If 0, it means login till the user closes the browser or the session is manually destroyed.
+ */
+ public $duration;
+ /**
+ * @var boolean whether the login or logout should proceed.
+ * Event handlers may modify this property to determine whether the login or logout should proceed.
+ * This property is only meaningful for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_BEFORE_LOGOUT]] events.
+ */
+ public $isValid = true;
}
diff --git a/framework/web/VerbFilter.php b/framework/web/VerbFilter.php
index 5841230b2a0..8b948e85f1c 100644
--- a/framework/web/VerbFilter.php
+++ b/framework/web/VerbFilter.php
@@ -45,64 +45,63 @@
*/
class VerbFilter extends Behavior
{
- /**
- * @var array this property defines the allowed request methods for each action.
- * For each action that should only support limited set of request methods
- * you add an entry with the action id as array key and an array of
- * allowed methods (e.g. GET, HEAD, PUT) as the value.
- * If an action is not listed all request methods are considered allowed.
- *
- * You can use '*' to stand for all actions. When an action is explicitly
- * specified, it takes precedence over the specification given by '*'.
- *
- * For example,
- *
- * ~~~
- * [
- * 'create' => ['get', 'post'],
- * 'update' => ['get', 'put', 'post'],
- * 'delete' => ['post', 'delete'],
- * '*' => ['get'],
- * ]
- * ~~~
- */
- public $actions = [];
+ /**
+ * @var array this property defines the allowed request methods for each action.
+ * For each action that should only support limited set of request methods
+ * you add an entry with the action id as array key and an array of
+ * allowed methods (e.g. GET, HEAD, PUT) as the value.
+ * If an action is not listed all request methods are considered allowed.
+ *
+ * You can use '*' to stand for all actions. When an action is explicitly
+ * specified, it takes precedence over the specification given by '*'.
+ *
+ * For example,
+ *
+ * ~~~
+ * [
+ * 'create' => ['get', 'post'],
+ * 'update' => ['get', 'put', 'post'],
+ * 'delete' => ['post', 'delete'],
+ * '*' => ['get'],
+ * ]
+ * ~~~
+ */
+ public $actions = [];
+ /**
+ * Declares event handlers for the [[owner]]'s events.
+ * @return array events (array keys) and the corresponding event handler methods (array values).
+ */
+ public function events()
+ {
+ return [Controller::EVENT_BEFORE_ACTION => 'beforeAction'];
+ }
- /**
- * Declares event handlers for the [[owner]]'s events.
- * @return array events (array keys) and the corresponding event handler methods (array values).
- */
- public function events()
- {
- return [Controller::EVENT_BEFORE_ACTION => 'beforeAction'];
- }
+ /**
+ * @param ActionEvent $event
+ * @return boolean
+ * @throws HttpException when the request method is not allowed.
+ */
+ public function beforeAction($event)
+ {
+ $action = $event->action->id;
+ if (isset($this->actions[$action])) {
+ $verbs = $this->actions[$action];
+ } elseif (isset($this->actions['*'])) {
+ $verbs = $this->actions['*'];
+ } else {
+ return $event->isValid;
+ }
- /**
- * @param ActionEvent $event
- * @return boolean
- * @throws HttpException when the request method is not allowed.
- */
- public function beforeAction($event)
- {
- $action = $event->action->id;
- if (isset($this->actions[$action])) {
- $verbs = $this->actions[$action];
- } elseif (isset($this->actions['*'])) {
- $verbs = $this->actions['*'];
- } else {
- return $event->isValid;
- }
+ $verb = Yii::$app->getRequest()->getMethod();
+ $allowed = array_map('strtoupper', $verbs);
+ if (!in_array($verb, $allowed)) {
+ $event->isValid = false;
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
+ Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
+ throw new MethodNotAllowedHttpException('Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed) . '.');
+ }
- $verb = Yii::$app->getRequest()->getMethod();
- $allowed = array_map('strtoupper', $verbs);
- if (!in_array($verb, $allowed)) {
- $event->isValid = false;
- // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
- Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
- throw new MethodNotAllowedHttpException('Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed) . '.');
- }
-
- return $event->isValid;
- }
+ return $event->isValid;
+ }
}
diff --git a/framework/web/View.php b/framework/web/View.php
index 7c95f5ef242..ab9e51b73b3 100644
--- a/framework/web/View.php
+++ b/framework/web/View.php
@@ -40,506 +40,508 @@
*/
class View extends \yii\base\View
{
- const EVENT_BEGIN_BODY = 'beginBody';
- /**
- * @event Event an event that is triggered by [[endBody()]].
- */
- const EVENT_END_BODY = 'endBody';
-
- /**
- * The location of registered JavaScript code block or files.
- * This means the location is in the head section.
- */
- const POS_HEAD = 1;
- /**
- * The location of registered JavaScript code block or files.
- * This means the location is at the beginning of the body section.
- */
- const POS_BEGIN = 2;
- /**
- * The location of registered JavaScript code block or files.
- * This means the location is at the end of the body section.
- */
- const POS_END = 3;
- /**
- * The location of registered JavaScript code block.
- * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`.
- */
- const POS_READY = 4;
- /**
- * The location of registered JavaScript code block.
- * This means the JavaScript code block will be enclosed within `jQuery(window).load()`.
- */
- const POS_LOAD = 5;
- /**
- * This is internally used as the placeholder for receiving the content registered for the head section.
- */
- const PH_HEAD = '';
- /**
- * This is internally used as the placeholder for receiving the content registered for the beginning of the body section.
- */
- const PH_BODY_BEGIN = '';
- /**
- * This is internally used as the placeholder for receiving the content registered for the end of the body section.
- */
- const PH_BODY_END = '';
-
- /**
- * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values
- * are the registered [[AssetBundle]] objects.
- * @see registerAssetBundle()
- */
- public $assetBundles = [];
- /**
- * @var string the page title
- */
- public $title;
- /**
- * @var array the registered meta tags.
- * @see registerMetaTag()
- */
- public $metaTags;
- /**
- * @var array the registered link tags.
- * @see registerLinkTag()
- */
- public $linkTags;
- /**
- * @var array the registered CSS code blocks.
- * @see registerCss()
- */
- public $css;
- /**
- * @var array the registered CSS files.
- * @see registerCssFile()
- */
- public $cssFiles;
- /**
- * @var array the registered JS code blocks
- * @see registerJs()
- */
- public $js;
- /**
- * @var array the registered JS files.
- * @see registerJsFile()
- */
- public $jsFiles;
-
- private $_assetManager;
-
-
- /**
- * Marks the position of an HTML head section.
- */
- public function head()
- {
- echo self::PH_HEAD;
- }
-
- /**
- * Marks the beginning of an HTML body section.
- */
- public function beginBody()
- {
- echo self::PH_BODY_BEGIN;
- $this->trigger(self::EVENT_BEGIN_BODY);
- }
-
- /**
- * Marks the ending of an HTML body section.
- */
- public function endBody()
- {
- $this->trigger(self::EVENT_END_BODY);
- echo self::PH_BODY_END;
-
- foreach (array_keys($this->assetBundles) as $bundle) {
- $this->registerAssetFiles($bundle);
- }
- }
-
- /**
- * Marks the ending of an HTML page.
- * @param boolean $ajaxMode whether the view is rendering in AJAX mode.
- * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions
- * will be rendered at the end of the view like normal scripts.
- */
- public function endPage($ajaxMode = false)
- {
- $this->trigger(self::EVENT_END_PAGE);
-
- $content = ob_get_clean();
-
- echo strtr($content, [
- self::PH_HEAD => $this->renderHeadHtml(),
- self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(),
- self::PH_BODY_END => $this->renderBodyEndHtml($ajaxMode),
- ]);
-
- $this->clear();
- }
-
- /**
- * Renders a view in response to an AJAX request.
- *
- * This method is similar to [[render()]] except that it will surround the view being rendered
- * with the calls of [[beginPage()]], [[head()]], [[beginBody()]], [[endBody()]] and [[endPage()]].
- * By doing so, the method is able to inject into the rendering result with JS/CSS scripts and files
- * that are registered with the view.
- *
- * @param string $view the view name. Please refer to [[render()]] 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.
- * @return string the rendering result
- * @see render()
- */
- public function renderAjax($view, $params = [], $context = null)
- {
- $viewFile = $this->findViewFile($view, $context);
-
- ob_start();
- ob_implicit_flush(false);
-
- $this->beginPage();
- $this->head();
- $this->beginBody();
- echo $this->renderFile($viewFile, $params, $context);
- $this->endBody();
- $this->endPage(true);
-
- return ob_get_clean();
- }
-
- /**
- * Registers the asset manager being used by this view object.
- * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component.
- */
- public function getAssetManager()
- {
- return $this->_assetManager ?: Yii::$app->getAssetManager();
- }
-
- /**
- * Sets the asset manager.
- * @param \yii\web\AssetManager $value the asset manager
- */
- public function setAssetManager($value)
- {
- $this->_assetManager = $value;
- }
-
- /**
- * Clears up the registered meta tags, link tags, css/js scripts and files.
- */
- public function clear()
- {
- $this->metaTags = null;
- $this->linkTags = null;
- $this->css = null;
- $this->cssFiles = null;
- $this->js = null;
- $this->jsFiles = null;
- }
-
- /**
- * Registers all files provided by an asset bundle including depending bundles files.
- * Removes a bundle from [[assetBundles]] once files are registered.
- * @param string $name name of the bundle to register
- */
- private function registerAssetFiles($name)
- {
- if (!isset($this->assetBundles[$name])) {
- return;
- }
- $bundle = $this->assetBundles[$name];
- if ($bundle) {
- foreach ($bundle->depends as $dep) {
- $this->registerAssetFiles($dep);
- }
- $bundle->registerAssetFiles($this);
- }
- unset($this->assetBundles[$name]);
- }
-
- /**
- * Registers the named asset bundle.
- * All dependent asset bundles will be registered.
- * @param string $name the name of the asset bundle.
- * @param integer|null $position if set, this forces a minimum position for javascript files.
- * This will adjust depending assets javascript file position or fail if requirement can not be met.
- * If this is null, asset bundles position settings will not be changed.
- * See [[registerJsFile]] for more details on javascript position.
- * @return AssetBundle the registered asset bundle instance
- * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected
- */
- public function registerAssetBundle($name, $position = null)
- {
- if (!isset($this->assetBundles[$name])) {
- $am = $this->getAssetManager();
- $bundle = $am->getBundle($name);
- $this->assetBundles[$name] = false;
- // register dependencies
- $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
- foreach ($bundle->depends as $dep) {
- $this->registerAssetBundle($dep, $pos);
- }
- $this->assetBundles[$name] = $bundle;
- } elseif ($this->assetBundles[$name] === false) {
- throw new InvalidConfigException("A circular dependency is detected for bundle '$name'.");
- } else {
- $bundle = $this->assetBundles[$name];
- }
-
- if ($position !== null) {
- $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
- if ($pos === null) {
- $bundle->jsOptions['position'] = $pos = $position;
- } elseif ($pos > $position) {
- throw new InvalidConfigException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'.");
- }
- // update position for all dependencies
- foreach ($bundle->depends as $dep) {
- $this->registerAssetBundle($dep, $pos);
- }
- }
- return $bundle;
- }
-
- /**
- * Registers a meta tag.
- * @param array $options the HTML attributes for the meta tag.
- * @param string $key the key that identifies the meta tag. If two meta tags are registered
- * with the same key, the latter will overwrite the former. If this is null, the new meta tag
- * will be appended to the existing ones.
- */
- public function registerMetaTag($options, $key = null)
- {
- if ($key === null) {
- $this->metaTags[] = Html::tag('meta', '', $options);
- } else {
- $this->metaTags[$key] = Html::tag('meta', '', $options);
- }
- }
-
- /**
- * Registers a link tag.
- * @param array $options the HTML attributes for the link tag.
- * @param string $key the key that identifies the link tag. If two link tags are registered
- * with the same key, the latter will overwrite the former. If this is null, the new link tag
- * will be appended to the existing ones.
- */
- public function registerLinkTag($options, $key = null)
- {
- if ($key === null) {
- $this->linkTags[] = Html::tag('link', '', $options);
- } else {
- $this->linkTags[$key] = Html::tag('link', '', $options);
- }
- }
-
- /**
- * Registers a CSS code block.
- * @param string $css the CSS code block to be registered
- * @param array $options the HTML attributes for the style tag.
- * @param string $key the key that identifies the CSS code block. If null, it will use
- * $css as the key. If two CSS code blocks are registered with the same key, the latter
- * will overwrite the former.
- */
- public function registerCss($css, $options = [], $key = null)
- {
- $key = $key ?: md5($css);
- $this->css[$key] = Html::style($css, $options);
- }
-
- /**
- * Registers a CSS file.
- * @param string $url the CSS file to be registered.
- * @param array $depends the names of the asset bundles that this CSS file depends on
- * @param array $options the HTML attributes for the link tag.
- * @param string $key the key that identifies the CSS script file. If null, it will use
- * $url as the key. If two CSS files are registered with the same key, the latter
- * will overwrite the former.
- */
- public function registerCssFile($url, $depends = [], $options = [], $key = null)
- {
- $url = Yii::getAlias($url);
- $key = $key ?: $url;
- if (empty($depends)) {
- $this->cssFiles[$key] = Html::cssFile($url, $options);
- } else {
- $am = Yii::$app->getAssetManager();
- $am->bundles[$key] = new AssetBundle([
- 'css' => [$url],
- 'cssOptions' => $options,
- 'depends' => (array)$depends,
- ]);
- $this->registerAssetBundle($key);
- }
- }
-
- /**
- * Registers a JS code block.
- * @param string $js the JS code block to be registered
- * @param integer $position the position at which the JS script tag should be inserted
- * in a page. The possible values are:
- *
- * - [[POS_HEAD]]: in the head section
- * - [[POS_BEGIN]]: at the beginning of the body section
- * - [[POS_END]]: at the end of the body section
- * - [[POS_LOAD]]: enclosed within jQuery(window).load().
- * Note that by using this position, the method will automatically register the jQuery js file.
- * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value.
- * Note that by using this position, the method will automatically register the jQuery js file.
- *
- * @param string $key the key that identifies the JS code block. If null, it will use
- * $js as the key. If two JS code blocks are registered with the same key, the latter
- * will overwrite the former.
- */
- public function registerJs($js, $position = self::POS_READY, $key = null)
- {
- $key = $key ?: md5($js);
- $this->js[$position][$key] = $js;
- if ($position === self::POS_READY || $position === self::POS_LOAD) {
- JqueryAsset::register($this);
- }
- }
-
- /**
- * Registers a JS file.
- * @param string $url the JS file to be registered.
- * @param array $depends the names of the asset bundles that this JS file depends on
- * @param array $options the HTML attributes for the script tag. A special option
- * named "position" is supported which specifies where the JS script tag should be inserted
- * in a page. The possible values of "position" are:
- *
- * - [[POS_HEAD]]: in the head section
- * - [[POS_BEGIN]]: at the beginning of the body section
- * - [[POS_END]]: at the end of the body section. This is the default value.
- *
- * @param string $key the key that identifies the JS script file. If null, it will use
- * $url as the key. If two JS files are registered with the same key, the latter
- * will overwrite the former.
- */
- public function registerJsFile($url, $depends = [], $options = [], $key = null)
- {
- $url = Yii::getAlias($url);
- $key = $key ?: $url;
- if (empty($depends)) {
- $position = isset($options['position']) ? $options['position'] : self::POS_END;
- unset($options['position']);
- $this->jsFiles[$position][$key] = Html::jsFile($url, $options);
- } else {
- $am = Yii::$app->getAssetManager();
- if (strpos($url, '/') !== 0 && strpos($url, '://') === false) {
- $url = Yii::$app->getRequest()->getBaseUrl() . '/' . $url;
- }
- $am->bundles[$key] = new AssetBundle([
- 'js' => [$url],
- 'jsOptions' => $options,
- 'depends' => (array)$depends,
- ]);
- $this->registerAssetBundle($key);
- }
- }
-
- /**
- * Renders the content to be inserted in the head section.
- * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
- * @return string the rendered content
- */
- protected function renderHeadHtml()
- {
- $lines = [];
- if (!empty($this->metaTags)) {
- $lines[] = implode("\n", $this->metaTags);
- }
-
- $request = Yii::$app->getRequest();
- if ($request instanceof \yii\web\Request && $request->enableCsrfValidation && !$request->getIsAjax()) {
- $lines[] = Html::tag('meta', '', ['name' => 'csrf-param', 'content' => $request->csrfParam]);
- $lines[] = Html::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]);
- }
-
- if (!empty($this->linkTags)) {
- $lines[] = implode("\n", $this->linkTags);
- }
- if (!empty($this->cssFiles)) {
- $lines[] = implode("\n", $this->cssFiles);
- }
- if (!empty($this->css)) {
- $lines[] = implode("\n", $this->css);
- }
- if (!empty($this->jsFiles[self::POS_HEAD])) {
- $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]);
- }
- if (!empty($this->js[self::POS_HEAD])) {
- $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), ['type' => 'text/javascript']);
- }
- return empty($lines) ? '' : implode("\n", $lines);
- }
-
- /**
- * Renders the content to be inserted at the beginning of the body section.
- * The content is rendered using the registered JS code blocks and files.
- * @return string the rendered content
- */
- protected function renderBodyBeginHtml()
- {
- $lines = [];
- if (!empty($this->jsFiles[self::POS_BEGIN])) {
- $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]);
- }
- if (!empty($this->js[self::POS_BEGIN])) {
- $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), ['type' => 'text/javascript']);
- }
- return empty($lines) ? '' : implode("\n", $lines);
- }
-
- /**
- * Renders the content to be inserted at the end of the body section.
- * The content is rendered using the registered JS code blocks and files.
- * @param boolean $ajaxMode whether the view is rendering in AJAX mode.
- * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions
- * will be rendered at the end of the view like normal scripts.
- * @return string the rendered content
- */
- protected function renderBodyEndHtml($ajaxMode)
- {
- $lines = [];
-
- if (!empty($this->jsFiles[self::POS_END])) {
- $lines[] = implode("\n", $this->jsFiles[self::POS_END]);
- }
-
- if ($ajaxMode) {
- $scripts = [];
- if (!empty($this->js[self::POS_END])) {
- $scripts[] = implode("\n", $this->js[self::POS_END]);
- }
- if (!empty($this->js[self::POS_READY])) {
- $scripts[] = implode("\n", $this->js[self::POS_READY]);
- }
- if (!empty($this->js[self::POS_LOAD])) {
- $scripts[] = implode("\n", $this->js[self::POS_LOAD]);
- }
- if (!empty($scripts)) {
- $lines[] = Html::script(implode("\n", $scripts), ['type' => 'text/javascript']);
- }
- } else {
- if (!empty($this->js[self::POS_END])) {
- $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), ['type' => 'text/javascript']);
- }
- if (!empty($this->js[self::POS_READY])) {
- $js = "jQuery(document).ready(function(){\n" . implode("\n", $this->js[self::POS_READY]) . "\n});";
- $lines[] = Html::script($js, ['type' => 'text/javascript']);
- }
- if (!empty($this->js[self::POS_LOAD])) {
- $js = "jQuery(window).load(function(){\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});";
- $lines[] = Html::script($js, ['type' => 'text/javascript']);
- }
- }
-
- return empty($lines) ? '' : implode("\n", $lines);
- }
+ const EVENT_BEGIN_BODY = 'beginBody';
+ /**
+ * @event Event an event that is triggered by [[endBody()]].
+ */
+ const EVENT_END_BODY = 'endBody';
+
+ /**
+ * The location of registered JavaScript code block or files.
+ * This means the location is in the head section.
+ */
+ const POS_HEAD = 1;
+ /**
+ * The location of registered JavaScript code block or files.
+ * This means the location is at the beginning of the body section.
+ */
+ const POS_BEGIN = 2;
+ /**
+ * The location of registered JavaScript code block or files.
+ * This means the location is at the end of the body section.
+ */
+ const POS_END = 3;
+ /**
+ * The location of registered JavaScript code block.
+ * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`.
+ */
+ const POS_READY = 4;
+ /**
+ * The location of registered JavaScript code block.
+ * This means the JavaScript code block will be enclosed within `jQuery(window).load()`.
+ */
+ const POS_LOAD = 5;
+ /**
+ * This is internally used as the placeholder for receiving the content registered for the head section.
+ */
+ const PH_HEAD = '';
+ /**
+ * This is internally used as the placeholder for receiving the content registered for the beginning of the body section.
+ */
+ const PH_BODY_BEGIN = '';
+ /**
+ * This is internally used as the placeholder for receiving the content registered for the end of the body section.
+ */
+ const PH_BODY_END = '';
+
+ /**
+ * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values
+ * are the registered [[AssetBundle]] objects.
+ * @see registerAssetBundle()
+ */
+ public $assetBundles = [];
+ /**
+ * @var string the page title
+ */
+ public $title;
+ /**
+ * @var array the registered meta tags.
+ * @see registerMetaTag()
+ */
+ public $metaTags;
+ /**
+ * @var array the registered link tags.
+ * @see registerLinkTag()
+ */
+ public $linkTags;
+ /**
+ * @var array the registered CSS code blocks.
+ * @see registerCss()
+ */
+ public $css;
+ /**
+ * @var array the registered CSS files.
+ * @see registerCssFile()
+ */
+ public $cssFiles;
+ /**
+ * @var array the registered JS code blocks
+ * @see registerJs()
+ */
+ public $js;
+ /**
+ * @var array the registered JS files.
+ * @see registerJsFile()
+ */
+ public $jsFiles;
+
+ private $_assetManager;
+
+ /**
+ * Marks the position of an HTML head section.
+ */
+ public function head()
+ {
+ echo self::PH_HEAD;
+ }
+
+ /**
+ * Marks the beginning of an HTML body section.
+ */
+ public function beginBody()
+ {
+ echo self::PH_BODY_BEGIN;
+ $this->trigger(self::EVENT_BEGIN_BODY);
+ }
+
+ /**
+ * Marks the ending of an HTML body section.
+ */
+ public function endBody()
+ {
+ $this->trigger(self::EVENT_END_BODY);
+ echo self::PH_BODY_END;
+
+ foreach (array_keys($this->assetBundles) as $bundle) {
+ $this->registerAssetFiles($bundle);
+ }
+ }
+
+ /**
+ * Marks the ending of an HTML page.
+ * @param boolean $ajaxMode whether the view is rendering in AJAX mode.
+ * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions
+ * will be rendered at the end of the view like normal scripts.
+ */
+ public function endPage($ajaxMode = false)
+ {
+ $this->trigger(self::EVENT_END_PAGE);
+
+ $content = ob_get_clean();
+
+ echo strtr($content, [
+ self::PH_HEAD => $this->renderHeadHtml(),
+ self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(),
+ self::PH_BODY_END => $this->renderBodyEndHtml($ajaxMode),
+ ]);
+
+ $this->clear();
+ }
+
+ /**
+ * Renders a view in response to an AJAX request.
+ *
+ * This method is similar to [[render()]] except that it will surround the view being rendered
+ * with the calls of [[beginPage()]], [[head()]], [[beginBody()]], [[endBody()]] and [[endPage()]].
+ * By doing so, the method is able to inject into the rendering result with JS/CSS scripts and files
+ * that are registered with the view.
+ *
+ * @param string $view the view name. Please refer to [[render()]] 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.
+ * @return string the rendering result
+ * @see render()
+ */
+ public function renderAjax($view, $params = [], $context = null)
+ {
+ $viewFile = $this->findViewFile($view, $context);
+
+ ob_start();
+ ob_implicit_flush(false);
+
+ $this->beginPage();
+ $this->head();
+ $this->beginBody();
+ echo $this->renderFile($viewFile, $params, $context);
+ $this->endBody();
+ $this->endPage(true);
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Registers the asset manager being used by this view object.
+ * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component.
+ */
+ public function getAssetManager()
+ {
+ return $this->_assetManager ?: Yii::$app->getAssetManager();
+ }
+
+ /**
+ * Sets the asset manager.
+ * @param \yii\web\AssetManager $value the asset manager
+ */
+ public function setAssetManager($value)
+ {
+ $this->_assetManager = $value;
+ }
+
+ /**
+ * Clears up the registered meta tags, link tags, css/js scripts and files.
+ */
+ public function clear()
+ {
+ $this->metaTags = null;
+ $this->linkTags = null;
+ $this->css = null;
+ $this->cssFiles = null;
+ $this->js = null;
+ $this->jsFiles = null;
+ }
+
+ /**
+ * Registers all files provided by an asset bundle including depending bundles files.
+ * Removes a bundle from [[assetBundles]] once files are registered.
+ * @param string $name name of the bundle to register
+ */
+ private function registerAssetFiles($name)
+ {
+ if (!isset($this->assetBundles[$name])) {
+ return;
+ }
+ $bundle = $this->assetBundles[$name];
+ if ($bundle) {
+ foreach ($bundle->depends as $dep) {
+ $this->registerAssetFiles($dep);
+ }
+ $bundle->registerAssetFiles($this);
+ }
+ unset($this->assetBundles[$name]);
+ }
+
+ /**
+ * Registers the named asset bundle.
+ * All dependent asset bundles will be registered.
+ * @param string $name the name of the asset bundle.
+ * @param integer|null $position if set, this forces a minimum position for javascript files.
+ * This will adjust depending assets javascript file position or fail if requirement can not be met.
+ * If this is null, asset bundles position settings will not be changed.
+ * See [[registerJsFile]] for more details on javascript position.
+ * @return AssetBundle the registered asset bundle instance
+ * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected
+ */
+ public function registerAssetBundle($name, $position = null)
+ {
+ if (!isset($this->assetBundles[$name])) {
+ $am = $this->getAssetManager();
+ $bundle = $am->getBundle($name);
+ $this->assetBundles[$name] = false;
+ // register dependencies
+ $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
+ foreach ($bundle->depends as $dep) {
+ $this->registerAssetBundle($dep, $pos);
+ }
+ $this->assetBundles[$name] = $bundle;
+ } elseif ($this->assetBundles[$name] === false) {
+ throw new InvalidConfigException("A circular dependency is detected for bundle '$name'.");
+ } else {
+ $bundle = $this->assetBundles[$name];
+ }
+
+ if ($position !== null) {
+ $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
+ if ($pos === null) {
+ $bundle->jsOptions['position'] = $pos = $position;
+ } elseif ($pos > $position) {
+ throw new InvalidConfigException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'.");
+ }
+ // update position for all dependencies
+ foreach ($bundle->depends as $dep) {
+ $this->registerAssetBundle($dep, $pos);
+ }
+ }
+
+ return $bundle;
+ }
+
+ /**
+ * Registers a meta tag.
+ * @param array $options the HTML attributes for the meta tag.
+ * @param string $key the key that identifies the meta tag. If two meta tags are registered
+ * with the same key, the latter will overwrite the former. If this is null, the new meta tag
+ * will be appended to the existing ones.
+ */
+ public function registerMetaTag($options, $key = null)
+ {
+ if ($key === null) {
+ $this->metaTags[] = Html::tag('meta', '', $options);
+ } else {
+ $this->metaTags[$key] = Html::tag('meta', '', $options);
+ }
+ }
+
+ /**
+ * Registers a link tag.
+ * @param array $options the HTML attributes for the link tag.
+ * @param string $key the key that identifies the link tag. If two link tags are registered
+ * with the same key, the latter will overwrite the former. If this is null, the new link tag
+ * will be appended to the existing ones.
+ */
+ public function registerLinkTag($options, $key = null)
+ {
+ if ($key === null) {
+ $this->linkTags[] = Html::tag('link', '', $options);
+ } else {
+ $this->linkTags[$key] = Html::tag('link', '', $options);
+ }
+ }
+
+ /**
+ * Registers a CSS code block.
+ * @param string $css the CSS code block to be registered
+ * @param array $options the HTML attributes for the style tag.
+ * @param string $key the key that identifies the CSS code block. If null, it will use
+ * $css as the key. If two CSS code blocks are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerCss($css, $options = [], $key = null)
+ {
+ $key = $key ?: md5($css);
+ $this->css[$key] = Html::style($css, $options);
+ }
+
+ /**
+ * Registers a CSS file.
+ * @param string $url the CSS file to be registered.
+ * @param array $depends the names of the asset bundles that this CSS file depends on
+ * @param array $options the HTML attributes for the link tag.
+ * @param string $key the key that identifies the CSS script file. If null, it will use
+ * $url as the key. If two CSS files are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerCssFile($url, $depends = [], $options = [], $key = null)
+ {
+ $url = Yii::getAlias($url);
+ $key = $key ?: $url;
+ if (empty($depends)) {
+ $this->cssFiles[$key] = Html::cssFile($url, $options);
+ } else {
+ $am = Yii::$app->getAssetManager();
+ $am->bundles[$key] = new AssetBundle([
+ 'css' => [$url],
+ 'cssOptions' => $options,
+ 'depends' => (array) $depends,
+ ]);
+ $this->registerAssetBundle($key);
+ }
+ }
+
+ /**
+ * Registers a JS code block.
+ * @param string $js the JS code block to be registered
+ * @param integer $position the position at which the JS script tag should be inserted
+ * in a page. The possible values are:
+ *
+ * - [[POS_HEAD]]: in the head section
+ * - [[POS_BEGIN]]: at the beginning of the body section
+ * - [[POS_END]]: at the end of the body section
+ * - [[POS_LOAD]]: enclosed within jQuery(window).load().
+ * Note that by using this position, the method will automatically register the jQuery js file.
+ * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value.
+ * Note that by using this position, the method will automatically register the jQuery js file.
+ *
+ * @param string $key the key that identifies the JS code block. If null, it will use
+ * $js as the key. If two JS code blocks are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerJs($js, $position = self::POS_READY, $key = null)
+ {
+ $key = $key ?: md5($js);
+ $this->js[$position][$key] = $js;
+ if ($position === self::POS_READY || $position === self::POS_LOAD) {
+ JqueryAsset::register($this);
+ }
+ }
+
+ /**
+ * Registers a JS file.
+ * @param string $url the JS file to be registered.
+ * @param array $depends the names of the asset bundles that this JS file depends on
+ * @param array $options the HTML attributes for the script tag. A special option
+ * named "position" is supported which specifies where the JS script tag should be inserted
+ * in a page. The possible values of "position" are:
+ *
+ * - [[POS_HEAD]]: in the head section
+ * - [[POS_BEGIN]]: at the beginning of the body section
+ * - [[POS_END]]: at the end of the body section. This is the default value.
+ *
+ * @param string $key the key that identifies the JS script file. If null, it will use
+ * $url as the key. If two JS files are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerJsFile($url, $depends = [], $options = [], $key = null)
+ {
+ $url = Yii::getAlias($url);
+ $key = $key ?: $url;
+ if (empty($depends)) {
+ $position = isset($options['position']) ? $options['position'] : self::POS_END;
+ unset($options['position']);
+ $this->jsFiles[$position][$key] = Html::jsFile($url, $options);
+ } else {
+ $am = Yii::$app->getAssetManager();
+ if (strpos($url, '/') !== 0 && strpos($url, '://') === false) {
+ $url = Yii::$app->getRequest()->getBaseUrl() . '/' . $url;
+ }
+ $am->bundles[$key] = new AssetBundle([
+ 'js' => [$url],
+ 'jsOptions' => $options,
+ 'depends' => (array) $depends,
+ ]);
+ $this->registerAssetBundle($key);
+ }
+ }
+
+ /**
+ * Renders the content to be inserted in the head section.
+ * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
+ * @return string the rendered content
+ */
+ protected function renderHeadHtml()
+ {
+ $lines = [];
+ if (!empty($this->metaTags)) {
+ $lines[] = implode("\n", $this->metaTags);
+ }
+
+ $request = Yii::$app->getRequest();
+ if ($request instanceof \yii\web\Request && $request->enableCsrfValidation && !$request->getIsAjax()) {
+ $lines[] = Html::tag('meta', '', ['name' => 'csrf-param', 'content' => $request->csrfParam]);
+ $lines[] = Html::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]);
+ }
+
+ if (!empty($this->linkTags)) {
+ $lines[] = implode("\n", $this->linkTags);
+ }
+ if (!empty($this->cssFiles)) {
+ $lines[] = implode("\n", $this->cssFiles);
+ }
+ if (!empty($this->css)) {
+ $lines[] = implode("\n", $this->css);
+ }
+ if (!empty($this->jsFiles[self::POS_HEAD])) {
+ $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]);
+ }
+ if (!empty($this->js[self::POS_HEAD])) {
+ $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), ['type' => 'text/javascript']);
+ }
+
+ return empty($lines) ? '' : implode("\n", $lines);
+ }
+
+ /**
+ * Renders the content to be inserted at the beginning of the body section.
+ * The content is rendered using the registered JS code blocks and files.
+ * @return string the rendered content
+ */
+ protected function renderBodyBeginHtml()
+ {
+ $lines = [];
+ if (!empty($this->jsFiles[self::POS_BEGIN])) {
+ $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]);
+ }
+ if (!empty($this->js[self::POS_BEGIN])) {
+ $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), ['type' => 'text/javascript']);
+ }
+
+ return empty($lines) ? '' : implode("\n", $lines);
+ }
+
+ /**
+ * Renders the content to be inserted at the end of the body section.
+ * The content is rendered using the registered JS code blocks and files.
+ * @param boolean $ajaxMode whether the view is rendering in AJAX mode.
+ * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions
+ * will be rendered at the end of the view like normal scripts.
+ * @return string the rendered content
+ */
+ protected function renderBodyEndHtml($ajaxMode)
+ {
+ $lines = [];
+
+ if (!empty($this->jsFiles[self::POS_END])) {
+ $lines[] = implode("\n", $this->jsFiles[self::POS_END]);
+ }
+
+ if ($ajaxMode) {
+ $scripts = [];
+ if (!empty($this->js[self::POS_END])) {
+ $scripts[] = implode("\n", $this->js[self::POS_END]);
+ }
+ if (!empty($this->js[self::POS_READY])) {
+ $scripts[] = implode("\n", $this->js[self::POS_READY]);
+ }
+ if (!empty($this->js[self::POS_LOAD])) {
+ $scripts[] = implode("\n", $this->js[self::POS_LOAD]);
+ }
+ if (!empty($scripts)) {
+ $lines[] = Html::script(implode("\n", $scripts), ['type' => 'text/javascript']);
+ }
+ } else {
+ if (!empty($this->js[self::POS_END])) {
+ $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), ['type' => 'text/javascript']);
+ }
+ if (!empty($this->js[self::POS_READY])) {
+ $js = "jQuery(document).ready(function () {\n" . implode("\n", $this->js[self::POS_READY]) . "\n});";
+ $lines[] = Html::script($js, ['type' => 'text/javascript']);
+ }
+ if (!empty($this->js[self::POS_LOAD])) {
+ $js = "jQuery(window).load(function () {\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});";
+ $lines[] = Html::script($js, ['type' => 'text/javascript']);
+ }
+ }
+
+ return empty($lines) ? '' : implode("\n", $lines);
+ }
}
diff --git a/framework/web/XmlResponseFormatter.php b/framework/web/XmlResponseFormatter.php
index 292424a94cc..3860c914161 100644
--- a/framework/web/XmlResponseFormatter.php
+++ b/framework/web/XmlResponseFormatter.php
@@ -24,75 +24,75 @@
*/
class XmlResponseFormatter extends Component implements ResponseFormatterInterface
{
- /**
- * @var string the Content-Type header for the response
- */
- public $contentType = 'application/xml';
- /**
- * @var string the XML version
- */
- public $version = '1.0';
- /**
- * @var string the XML encoding. If not set, it will use the value of [[Response::charset]].
- */
- public $encoding;
- /**
- * @var string the name of the root element.
- */
- public $rootTag = 'response';
- /**
- * @var string the name of the elements that represent the array elements with numeric keys.
- */
- public $itemTag = 'item';
+ /**
+ * @var string the Content-Type header for the response
+ */
+ public $contentType = 'application/xml';
+ /**
+ * @var string the XML version
+ */
+ public $version = '1.0';
+ /**
+ * @var string the XML encoding. If not set, it will use the value of [[Response::charset]].
+ */
+ public $encoding;
+ /**
+ * @var string the name of the root element.
+ */
+ public $rootTag = 'response';
+ /**
+ * @var string the name of the elements that represent the array elements with numeric keys.
+ */
+ public $itemTag = 'item';
- /**
- * Formats the specified response.
- * @param Response $response the response to be formatted.
- */
- public function format($response)
- {
- $response->getHeaders()->set('Content-Type', $this->contentType);
- $dom = new DOMDocument($this->version, $this->encoding === null ? $response->charset : $this->encoding);
- $root = new DOMElement($this->rootTag);
- $dom->appendChild($root);
- $this->buildXml($root, $response->data);
- $response->content = $dom->saveXML();
- }
+ /**
+ * Formats the specified response.
+ * @param Response $response the response to be formatted.
+ */
+ public function format($response)
+ {
+ $response->getHeaders()->set('Content-Type', $this->contentType);
+ $dom = new DOMDocument($this->version, $this->encoding === null ? $response->charset : $this->encoding);
+ $root = new DOMElement($this->rootTag);
+ $dom->appendChild($root);
+ $this->buildXml($root, $response->data);
+ $response->content = $dom->saveXML();
+ }
- /**
- * @param DOMElement $element
- * @param mixed $data
- */
- protected function buildXml($element, $data)
- {
- if (is_object($data)) {
- $child = new DOMElement(StringHelper::basename(get_class($data)));
- $element->appendChild($child);
- if ($data instanceof Arrayable) {
- $this->buildXml($child, $data->toArray());
- } else {
- $array = [];
- foreach ($data as $name => $value) {
- $array[$name] = $value;
- }
- $this->buildXml($child, $array);
- }
- } elseif (is_array($data)) {
- foreach ($data as $name => $value) {
- if (is_int($name) && is_object($value)) {
- $this->buildXml($element, $value);
- } elseif (is_array($value) || is_object($value)) {
- $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
- $element->appendChild($child);
- $this->buildXml($child, $value);
- } else {
- $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
- $element->appendChild($child);
- $child->appendChild(new DOMText((string)$value));
- }
- }
- } else {
- $element->appendChild(new DOMText((string)$data));
- }
- }
+ /**
+ * @param DOMElement $element
+ * @param mixed $data
+ */
+ protected function buildXml($element, $data)
+ {
+ if (is_object($data)) {
+ $child = new DOMElement(StringHelper::basename(get_class($data)));
+ $element->appendChild($child);
+ if ($data instanceof Arrayable) {
+ $this->buildXml($child, $data->toArray());
+ } else {
+ $array = [];
+ foreach ($data as $name => $value) {
+ $array[$name] = $value;
+ }
+ $this->buildXml($child, $array);
+ }
+ } elseif (is_array($data)) {
+ foreach ($data as $name => $value) {
+ if (is_int($name) && is_object($value)) {
+ $this->buildXml($element, $value);
+ } elseif (is_array($value) || is_object($value)) {
+ $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
+ $element->appendChild($child);
+ $this->buildXml($child, $value);
+ } else {
+ $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
+ $element->appendChild($child);
+ $child->appendChild(new DOMText((string) $value));
+ }
+ }
+ } else {
+ $element->appendChild(new DOMText((string) $data));
+ }
+ }
}
diff --git a/framework/web/YiiAsset.php b/framework/web/YiiAsset.php
index d38b7113ade..db7e85ec206 100644
--- a/framework/web/YiiAsset.php
+++ b/framework/web/YiiAsset.php
@@ -15,11 +15,11 @@
*/
class YiiAsset extends AssetBundle
{
- public $sourcePath = '@yii/assets';
- public $js = [
- 'yii.js',
- ];
- public $depends = [
- 'yii\web\JqueryAsset',
- ];
+ public $sourcePath = '@yii/assets';
+ public $js = [
+ 'yii.js',
+ ];
+ public $depends = [
+ 'yii\web\JqueryAsset',
+ ];
}
diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php
index c93b30b7ddf..92c09ae1dda 100644
--- a/framework/widgets/ActiveField.php
+++ b/framework/widgets/ActiveField.php
@@ -21,649 +21,667 @@
*/
class ActiveField extends Component
{
- /**
- * @var ActiveForm the form that this field is associated with.
- */
- public $form;
- /**
- * @var Model the data model that this field is associated with
- */
- public $model;
- /**
- * @var string the model attribute that this field is associated with
- */
- public $attribute;
- /**
- * @var array the HTML attributes (name-value pairs) for the field container tag.
- * The values will be HTML-encoded using [[Html::encode()]].
- * If a value is null, the corresponding attribute will not be rendered.
- * The following special options are recognized:
- *
- * - tag: the tag name of the container element. Defaults to "div".
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'form-group'];
- /**
- * @var string the template that is used to arrange the label, the input field, the error message and the hint text.
- * The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}`, `{error}` and `{hint}`.
- */
- public $template = "{label}\n{input}\n{hint}\n{error}";
- /**
- * @var array the default options for the input tags. The parameter passed to individual input methods
- * (e.g. [[textInput()]]) will be merged with this property when rendering the input tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $inputOptions = ['class' => 'form-control'];
- /**
- * @var array the default options for the error tags. The parameter passed to [[error()]] will be
- * merged with this property when rendering the error tag.
- * The following special options are recognized:
- *
- * - tag: the tag name of the container element. Defaults to "div".
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $errorOptions = ['class' => 'help-block'];
- /**
- * @var array the default options for the label tags. The parameter passed to [[label()]] will be
- * merged with this property when rendering the label tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $labelOptions = ['class' => 'control-label'];
- /**
- * @var array the default options for the hint tags. The parameter passed to [[hint()]] will be
- * merged with this property when rendering the hint tag.
- * The following special options are recognized:
- *
- * - tag: the tag name of the container element. Defaults to "div".
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $hintOptions = ['class' => 'hint-block'];
- /**
- * @var boolean whether to enable client-side data validation.
- * If not set, it will take the value of [[ActiveForm::enableClientValidation]].
- */
- public $enableClientValidation;
- /**
- * @var boolean whether to enable AJAX-based data validation.
- * If not set, it will take the value of [[ActiveForm::enableAjaxValidation]].
- */
- public $enableAjaxValidation;
- /**
- * @var boolean whether to perform validation when the input field loses focus and its value is found changed.
- * If not set, it will take the value of [[ActiveForm::validateOnChange]].
- */
- public $validateOnChange;
- /**
- * @var boolean whether to perform validation while the user is typing in the input field.
- * If not set, it will take the value of [[ActiveForm::validateOnType]].
- * @see validationDelay
- */
- public $validateOnType;
- /**
- * @var integer number of milliseconds that the validation should be delayed when the input field
- * is changed or the user types in the field.
- * If not set, it will take the value of [[ActiveForm::validationDelay]].
- */
- public $validationDelay;
- /**
- * @var array the jQuery selectors for selecting the container, input and error tags.
- * The array keys should be "container", "input", and/or "error", and the array values
- * are the corresponding selectors. For example, `['input' => '#my-input']`.
- *
- * The container selector is used under the context of the form, while the input and the error
- * selectors are used under the context of the container.
- *
- * You normally do not need to set this property as the default selectors should work well for most cases.
- */
- public $selectors;
- /**
- * @var array different parts of the field (e.g. input, label). This will be used together with
- * [[template]] to generate the final field HTML code. The keys are the token names in [[template]],
- * while the values are the corresponding HTML code. Valid tokens include `{input}`, `{label}` and `{error}`.
- * Note that you normally don't need to access this property directly as
- * it is maintained by various methods of this class.
- */
- public $parts = [];
-
-
- /**
- * PHP magic method that returns the string representation of this object.
- * @return string the string representation of this object.
- */
- public function __toString()
- {
- // __toString cannot throw exception
- // use trigger_error to bypass this limitation
- try {
- return $this->render();
- } catch (\Exception $e) {
- trigger_error($e->getMessage() . "\n\n" . $e->getTraceAsString());
- return '';
- }
- }
-
- /**
- * Renders the whole field.
- * This method will generate the label, error tag, input tag and hint tag (if any), and
- * assemble them into HTML according to [[template]].
- * @param string|callable $content the content within the field container.
- * If null (not set), the default methods will be called to generate the label, error tag and input tag,
- * and use them as the content.
- * If a callable, it will be called to generate the content. The signature of the callable should be:
- *
- * ~~~
- * function ($field) {
- * return $html;
- * }
- * ~~~
- *
- * @return string the rendering result
- */
- public function render($content = null)
- {
- if ($content === null) {
- if (!isset($this->parts['{input}'])) {
- $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $this->inputOptions);
- }
- if (!isset($this->parts['{label}'])) {
- $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $this->labelOptions);
- }
- if (!isset($this->parts['{error}'])) {
- $this->parts['{error}'] = Html::error($this->model, $this->attribute, $this->errorOptions);
- }
- if (!isset($this->parts['{hint}'])) {
- $this->parts['{hint}'] = '';
- }
- $content = strtr($this->template, $this->parts);
- } elseif (!is_string($content)) {
- $content = call_user_func($content, $this);
- }
- return $this->begin() . "\n" . $content . "\n" . $this->end();
- }
-
- /**
- * Renders the opening tag of the field container.
- * @return string the rendering result.
- */
- public function begin()
- {
- $clientOptions = $this->getClientOptions();
- if (!empty($clientOptions)) {
- $this->form->attributes[$this->attribute] = $clientOptions;
- }
-
- $inputID = Html::getInputId($this->model, $this->attribute);
- $attribute = Html::getAttributeName($this->attribute);
- $options = $this->options;
- $class = isset($options['class']) ? [$options['class']] : [];
- $class[] = "field-$inputID";
- if ($this->model->isAttributeRequired($attribute)) {
- $class[] = $this->form->requiredCssClass;
- }
- if ($this->model->hasErrors($attribute)) {
- $class[] = $this->form->errorCssClass;
- }
- $options['class'] = implode(' ', $class);
- $tag = ArrayHelper::remove($options, 'tag', 'div');
-
- return Html::beginTag($tag, $options);
- }
-
- /**
- * Renders the closing tag of the field container.
- * @return string the rendering result.
- */
- public function end()
- {
- return Html::endTag(isset($this->options['tag']) ? $this->options['tag'] : 'div');
- }
-
- /**
- * Generates a label tag for [[attribute]].
- * @param string $label the label to use. If null, it will be generated via [[Model::getAttributeLabel()]].
- * Note that this will NOT be [[Html::encode()|encoded]].
- * @param array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]].
- * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
- * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
- * @return static the field object itself
- */
- public function label($label = null, $options = [])
- {
- $options = array_merge($this->labelOptions, $options);
- if ($label !== null) {
- $options['label'] = $label;
- }
- $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options);
- return $this;
- }
-
- /**
- * Generates a tag that contains the first validation error of [[attribute]].
- * Note that even if there is no validation error, this method will still return an empty error tag.
- * @param array $options the tag options in terms of name-value pairs. It will be merged with [[errorOptions]].
- * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
- * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
- *
- * The following options are specially handled:
- *
- * - tag: this specifies the tag name. If not set, "div" will be used.
- *
- * @return static the field object itself
- */
- public function error($options = [])
- {
- $options = array_merge($this->errorOptions, $options);
- $this->parts['{error}'] = Html::error($this->model, $this->attribute, $options);
- return $this;
- }
-
- /**
- * Renders the hint tag.
- * @param string $content the hint content. It will NOT be HTML-encoded.
- * @param array $options the tag options in terms of name-value pairs. These will be rendered as
- * the attributes of the hint tag. The values will be HTML-encoded using [[Html::encode()]].
- *
- * The following options are specially handled:
- *
- * - tag: this specifies the tag name. If not set, "div" will be used.
- *
- * @return static the field object itself
- */
- public function hint($content, $options = [])
- {
- $options = array_merge($this->hintOptions, $options);
- $tag = ArrayHelper::remove($options, 'tag', 'div');
- $this->parts['{hint}'] = Html::tag($tag, $content, $options);
- return $this;
- }
-
- /**
- * Renders an input tag.
- * @param string $type the input type (e.g. 'text', 'password')
- * @param array $options the tag options in terms of name-value pairs. These will be rendered as
- * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
- * @return static the field object itself
- */
- public function input($type, $options = [])
- {
- $options = array_merge($this->inputOptions, $options);
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
- return $this;
- }
-
- /**
- * Renders a text input.
- * This method will generate the "name" and "value" tag attributes automatically for the model attribute
- * unless they are explicitly specified in `$options`.
- * @param array $options the tag options in terms of name-value pairs. These will be rendered as
- * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
- * @return static the field object itself
- */
- public function textInput($options = [])
- {
- $options = array_merge($this->inputOptions, $options);
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
- return $this;
- }
-
- /**
- * Renders a password input.
- * This method will generate the "name" and "value" tag attributes automatically for the model attribute
- * unless they are explicitly specified in `$options`.
- * @param array $options the tag options in terms of name-value pairs. These will be rendered as
- * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
- * @return static the field object itself
- */
- public function passwordInput($options = [])
- {
- $options = array_merge($this->inputOptions, $options);
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
- return $this;
- }
-
- /**
- * Renders a file input.
- * This method will generate the "name" and "value" tag attributes automatically for the model attribute
- * unless they are explicitly specified in `$options`.
- * @param array $options the tag options in terms of name-value pairs. These will be rendered as
- * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
- * @return static the field object itself
- */
- public function fileInput($options = [])
- {
- // https://github.com/yiisoft/yii2/pull/795
- if ($this->inputOptions !== ['class' => 'form-control']) {
- $options = array_merge($this->inputOptions, $options);
- }
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
- return $this;
- }
-
- /**
- * Renders a text area.
- * The model attribute value will be used as the content in the textarea.
- * @param array $options the tag options in terms of name-value pairs. These will be rendered as
- * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
- * @return static the field object itself
- */
- public function textarea($options = [])
- {
- $options = array_merge($this->inputOptions, $options);
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
- return $this;
- }
-
- /**
- * Renders a radio button.
- * This method will generate the "checked" tag attribute according to the model attribute value.
- * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
- *
- * - uncheck: string, the value associated with the uncheck state of the radio button. If not set,
- * it will take the default value '0'. This method will render a hidden input so that if the radio button
- * is not checked and is submitted, the value of this attribute will still be submitted to the server
- * via the hidden input.
- * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass
- * in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] it to prevent XSS attacks.
- * When this option is specified, the radio button will be enclosed by a label tag.
- * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
- *
- * The rest of the options will be rendered as the attributes of the resulting tag. The values will
- * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
- * @param boolean $enclosedByLabel whether to enclose the radio within the label.
- * If true, the method will still use [[template]] to layout the checkbox and the error message
- * except that the radio is enclosed by the label tag.
- * @return static the field object itself
- */
- public function radio($options = [], $enclosedByLabel = true)
- {
- if ($enclosedByLabel) {
- if (!isset($options['label'])) {
- $attribute = Html::getAttributeName($this->attribute);
- $options['label'] = Html::encode($this->model->getAttributeLabel($attribute));
- }
- $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
- $this->parts['{label}'] = '';
- } else {
- $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
- }
- $this->adjustLabelFor($options);
- return $this;
- }
-
- /**
- * Renders a checkbox.
- * This method will generate the "checked" tag attribute according to the model attribute value.
- * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
- *
- * - uncheck: string, the value associated with the uncheck state of the radio button. If not set,
- * it will take the default value '0'. This method will render a hidden input so that if the radio button
- * is not checked and is submitted, the value of this attribute will still be submitted to the server
- * via the hidden input.
- * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass
- * in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] it to prevent XSS attacks.
- * When this option is specified, the checkbox will be enclosed by a label tag.
- * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
- *
- * The rest of the options will be rendered as the attributes of the resulting tag. The values will
- * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
- * @param boolean $enclosedByLabel whether to enclose the checkbox within the label.
- * If true, the method will still use [[template]] to layout the checkbox and the error message
- * except that the checkbox is enclosed by the label tag.
- * @return static the field object itself
- */
- public function checkbox($options = [], $enclosedByLabel = true)
- {
- if ($enclosedByLabel) {
- if (!isset($options['label'])) {
- $attribute = Html::getAttributeName($this->attribute);
- $options['label'] = Html::encode($this->model->getAttributeLabel($attribute));
- }
- $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
- $this->parts['{label}'] = '';
- } else {
- $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
- }
- $this->adjustLabelFor($options);
- return $this;
- }
-
- /**
- * Renders a drop-down list.
- * The selection of the drop-down list is taken from the value of the model attribute.
- * @param array $items the option data items. The array keys are option values, and the array values
- * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
- * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
- * If you have a list of data models, you may convert them into the format described above using
- * [[ArrayHelper::map()]].
- *
- * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
- * the labels will also be HTML-encoded.
- * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
- *
- * - prompt: string, a prompt text to be displayed as the first option;
- * - options: array, the attributes for the select option tags. The array keys must be valid option values,
- * and the array values are the extra attributes for the corresponding option tags. For example,
- *
- * ~~~
- * [
- * 'value1' => ['disabled' => true],
- * 'value2' => ['label' => 'value 2'],
- * ];
- * ~~~
- *
- * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
- * except that the array keys represent the optgroup labels specified in $items.
- *
- * The rest of the options will be rendered as the attributes of the resulting tag. The values will
- * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
- *
- * @return static the field object itself
- */
- public function dropDownList($items, $options = [])
- {
- $options = array_merge($this->inputOptions, $options);
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
- return $this;
- }
-
- /**
- * Renders a list box.
- * The selection of the list box is taken from the value of the model attribute.
- * @param array $items the option data items. The array keys are option values, and the array values
- * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
- * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
- * If you have a list of data models, you may convert them into the format described above using
- * [[\yii\helpers\ArrayHelper::map()]].
- *
- * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
- * the labels will also be HTML-encoded.
- * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
- *
- * - prompt: string, a prompt text to be displayed as the first option;
- * - options: array, the attributes for the select option tags. The array keys must be valid option values,
- * and the array values are the extra attributes for the corresponding option tags. For example,
- *
- * ~~~
- * [
- * 'value1' => ['disabled' => true],
- * 'value2' => ['label' => 'value 2'],
- * ];
- * ~~~
- *
- * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
- * except that the array keys represent the optgroup labels specified in $items.
- * - unselect: string, the value that will be submitted when no option is selected.
- * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple
- * mode, we can still obtain the posted unselect value.
- *
- * The rest of the options will be rendered as the attributes of the resulting tag. The values will
- * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
- *
- * @return static the field object itself
- */
- public function listBox($items, $options = [])
- {
- $options = array_merge($this->inputOptions, $options);
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
- return $this;
- }
-
- /**
- * Renders a list of checkboxes.
- * A checkbox list allows multiple selection, like [[listBox()]].
- * As a result, the corresponding submitted value is an array.
- * The selection of the checkbox list is taken from the value of the model attribute.
- * @param array $items the data item used to generate the checkboxes.
- * The array values are the labels, while the array keys are the corresponding checkbox values.
- * Note that the labels will NOT be HTML-encoded, while the values will.
- * @param array $options options (name => config) for the checkbox list. The following options are specially handled:
- *
- * - unselect: string, the value that should be submitted when none of the checkboxes is selected.
- * By setting this option, a hidden input will be generated.
- * - separator: string, the HTML code that separates items.
- * - item: callable, a callback that can be used to customize the generation of the HTML code
- * corresponding to a single item in $items. The signature of this callback must be:
- *
- * ~~~
- * function ($index, $label, $name, $checked, $value)
- * ~~~
- *
- * where $index is the zero-based index of the checkbox in the whole list; $label
- * is the label for the checkbox; and $name, $value and $checked represent the name,
- * value and the checked status of the checkbox input.
- * @return static the field object itself
- */
- public function checkboxList($items, $options = [])
- {
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
- return $this;
- }
-
- /**
- * Renders a list of radio buttons.
- * A radio button list is like a checkbox list, except that it only allows single selection.
- * The selection of the radio buttons is taken from the value of the model attribute.
- * @param array $items the data item used to generate the radio buttons.
- * The array keys are the labels, while the array values are the corresponding radio button values.
- * Note that the labels will NOT be HTML-encoded, while the values will.
- * @param array $options options (name => config) for the radio button list. The following options are specially handled:
- *
- * - unselect: string, the value that should be submitted when none of the radio buttons is selected.
- * By setting this option, a hidden input will be generated.
- * - separator: string, the HTML code that separates items.
- * - item: callable, a callback that can be used to customize the generation of the HTML code
- * corresponding to a single item in $items. The signature of this callback must be:
- *
- * ~~~
- * function ($index, $label, $name, $checked, $value)
- * ~~~
- *
- * where $index is the zero-based index of the radio button in the whole list; $label
- * is the label for the radio button; and $name, $value and $checked represent the name,
- * value and the checked status of the radio button input.
- * @return static the field object itself
- */
- public function radioList($items, $options = [])
- {
- $this->adjustLabelFor($options);
- $this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options);
- return $this;
- }
-
- /**
- * Renders a widget as the input of the field.
- *
- * Note that the widget must have both `model` and `attribute` properties. They will
- * be initialized with [[model]] and [[attribute]] of this field, respectively.
- *
- * If you want to use a widget that does not have `model` and `attribute` properties,
- * please use [[render()]] instead.
- *
- * @param string $class the widget class name
- * @param array $config name-value pairs that will be used to initialize the widget
- * @return static the field object itself
- */
- public function widget($class, $config = [])
- {
- /** @var \yii\base\Widget $class */
- $config['model'] = $this->model;
- $config['attribute'] = $this->attribute;
- $config['view'] = $this->form->getView();
- $this->parts['{input}'] = $class::widget($config);
- return $this;
- }
-
- /**
- * Adjusts the "for" attribute for the label based on the input options.
- * @param array $options the input options
- */
- protected function adjustLabelFor($options)
- {
- if (isset($options['id']) && !isset($this->labelOptions['for'])) {
- $this->labelOptions['for'] = $options['id'];
- }
- }
-
- /**
- * Returns the JS options for the field.
- * @return array the JS options
- */
- protected function getClientOptions()
- {
- $attribute = Html::getAttributeName($this->attribute);
- if (!in_array($attribute, $this->model->activeAttributes(), true)) {
- return [];
- }
-
- $options = [];
-
- $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
- if ($enableClientValidation) {
- $validators = [];
- foreach ($this->model->getActiveValidators($attribute) as $validator) {
- /** @var \yii\validators\Validator $validator */
- $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView());
- if ($validator->enableClientValidation && $js != '') {
- $validators[] = $js;
- }
- }
- if (!empty($validators)) {
- $options['validate'] = new JsExpression("function(attribute, value, messages) {" . implode('', $validators) . '}');
- }
- }
-
- $enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
- if ($enableAjaxValidation) {
- $options['enableAjaxValidation'] = 1;
- }
-
- if ($enableClientValidation && !empty($options['validate']) || $enableAjaxValidation) {
- $inputID = Html::getInputId($this->model, $this->attribute);
- $options['name'] = $inputID;
- foreach (['validateOnChange', 'validateOnType', 'validationDelay'] as $name) {
- $options[$name] = $this->$name === null ? $this->form->$name : $this->$name;
- }
- $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID";
- $options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID";
- if (isset($this->errorOptions['class'])) {
- $options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY));
- } else {
- $options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span';
- }
- return $options;
- } else {
- return [];
- }
- }
+ /**
+ * @var ActiveForm the form that this field is associated with.
+ */
+ public $form;
+ /**
+ * @var Model the data model that this field is associated with
+ */
+ public $model;
+ /**
+ * @var string the model attribute that this field is associated with
+ */
+ public $attribute;
+ /**
+ * @var array the HTML attributes (name-value pairs) for the field container tag.
+ * The values will be HTML-encoded using [[Html::encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
+ * The following special options are recognized:
+ *
+ * - tag: the tag name of the container element. Defaults to "div".
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'form-group'];
+ /**
+ * @var string the template that is used to arrange the label, the input field, the error message and the hint text.
+ * The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}`, `{error}` and `{hint}`.
+ */
+ public $template = "{label}\n{input}\n{hint}\n{error}";
+ /**
+ * @var array the default options for the input tags. The parameter passed to individual input methods
+ * (e.g. [[textInput()]]) will be merged with this property when rendering the input tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $inputOptions = ['class' => 'form-control'];
+ /**
+ * @var array the default options for the error tags. The parameter passed to [[error()]] will be
+ * merged with this property when rendering the error tag.
+ * The following special options are recognized:
+ *
+ * - tag: the tag name of the container element. Defaults to "div".
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $errorOptions = ['class' => 'help-block'];
+ /**
+ * @var array the default options for the label tags. The parameter passed to [[label()]] will be
+ * merged with this property when rendering the label tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $labelOptions = ['class' => 'control-label'];
+ /**
+ * @var array the default options for the hint tags. The parameter passed to [[hint()]] will be
+ * merged with this property when rendering the hint tag.
+ * The following special options are recognized:
+ *
+ * - tag: the tag name of the container element. Defaults to "div".
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $hintOptions = ['class' => 'hint-block'];
+ /**
+ * @var boolean whether to enable client-side data validation.
+ * If not set, it will take the value of [[ActiveForm::enableClientValidation]].
+ */
+ public $enableClientValidation;
+ /**
+ * @var boolean whether to enable AJAX-based data validation.
+ * If not set, it will take the value of [[ActiveForm::enableAjaxValidation]].
+ */
+ public $enableAjaxValidation;
+ /**
+ * @var boolean whether to perform validation when the input field loses focus and its value is found changed.
+ * If not set, it will take the value of [[ActiveForm::validateOnChange]].
+ */
+ public $validateOnChange;
+ /**
+ * @var boolean whether to perform validation while the user is typing in the input field.
+ * If not set, it will take the value of [[ActiveForm::validateOnType]].
+ * @see validationDelay
+ */
+ public $validateOnType;
+ /**
+ * @var integer number of milliseconds that the validation should be delayed when the input field
+ * is changed or the user types in the field.
+ * If not set, it will take the value of [[ActiveForm::validationDelay]].
+ */
+ public $validationDelay;
+ /**
+ * @var array the jQuery selectors for selecting the container, input and error tags.
+ * The array keys should be "container", "input", and/or "error", and the array values
+ * are the corresponding selectors. For example, `['input' => '#my-input']`.
+ *
+ * The container selector is used under the context of the form, while the input and the error
+ * selectors are used under the context of the container.
+ *
+ * You normally do not need to set this property as the default selectors should work well for most cases.
+ */
+ public $selectors;
+ /**
+ * @var array different parts of the field (e.g. input, label). This will be used together with
+ * [[template]] to generate the final field HTML code. The keys are the token names in [[template]],
+ * while the values are the corresponding HTML code. Valid tokens include `{input}`, `{label}` and `{error}`.
+ * Note that you normally don't need to access this property directly as
+ * it is maintained by various methods of this class.
+ */
+ public $parts = [];
+
+
+ /**
+ * PHP magic method that returns the string representation of this object.
+ * @return string the string representation of this object.
+ */
+ public function __toString()
+ {
+ // __toString cannot throw exception
+ // use trigger_error to bypass this limitation
+ try {
+ return $this->render();
+ } catch (\Exception $e) {
+ trigger_error($e->getMessage() . "\n\n" . $e->getTraceAsString());
+
+ return '';
+ }
+ }
+
+ /**
+ * Renders the whole field.
+ * This method will generate the label, error tag, input tag and hint tag (if any), and
+ * assemble them into HTML according to [[template]].
+ * @param string|callable $content the content within the field container.
+ * If null (not set), the default methods will be called to generate the label, error tag and input tag,
+ * and use them as the content.
+ * If a callable, it will be called to generate the content. The signature of the callable should be:
+ *
+ * ~~~
+ * function ($field) {
+ * return $html;
+ * }
+ * ~~~
+ *
+ * @return string the rendering result
+ */
+ public function render($content = null)
+ {
+ if ($content === null) {
+ if (!isset($this->parts['{input}'])) {
+ $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $this->inputOptions);
+ }
+ if (!isset($this->parts['{label}'])) {
+ $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $this->labelOptions);
+ }
+ if (!isset($this->parts['{error}'])) {
+ $this->parts['{error}'] = Html::error($this->model, $this->attribute, $this->errorOptions);
+ }
+ if (!isset($this->parts['{hint}'])) {
+ $this->parts['{hint}'] = '';
+ }
+ $content = strtr($this->template, $this->parts);
+ } elseif (!is_string($content)) {
+ $content = call_user_func($content, $this);
+ }
+
+ return $this->begin() . "\n" . $content . "\n" . $this->end();
+ }
+
+ /**
+ * Renders the opening tag of the field container.
+ * @return string the rendering result.
+ */
+ public function begin()
+ {
+ $clientOptions = $this->getClientOptions();
+ if (!empty($clientOptions)) {
+ $this->form->attributes[$this->attribute] = $clientOptions;
+ }
+
+ $inputID = Html::getInputId($this->model, $this->attribute);
+ $attribute = Html::getAttributeName($this->attribute);
+ $options = $this->options;
+ $class = isset($options['class']) ? [$options['class']] : [];
+ $class[] = "field-$inputID";
+ if ($this->model->isAttributeRequired($attribute)) {
+ $class[] = $this->form->requiredCssClass;
+ }
+ if ($this->model->hasErrors($attribute)) {
+ $class[] = $this->form->errorCssClass;
+ }
+ $options['class'] = implode(' ', $class);
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+
+ return Html::beginTag($tag, $options);
+ }
+
+ /**
+ * Renders the closing tag of the field container.
+ * @return string the rendering result.
+ */
+ public function end()
+ {
+ return Html::endTag(isset($this->options['tag']) ? $this->options['tag'] : 'div');
+ }
+
+ /**
+ * Generates a label tag for [[attribute]].
+ * @param string $label the label to use. If null, it will be generated via [[Model::getAttributeLabel()]].
+ * Note that this will NOT be [[Html::encode()|encoded]].
+ * @param array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]].
+ * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
+ * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ * @return static the field object itself
+ */
+ public function label($label = null, $options = [])
+ {
+ $options = array_merge($this->labelOptions, $options);
+ if ($label !== null) {
+ $options['label'] = $label;
+ }
+ $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options);
+
+ return $this;
+ }
+
+ /**
+ * Generates a tag that contains the first validation error of [[attribute]].
+ * Note that even if there is no validation error, this method will still return an empty error tag.
+ * @param array $options the tag options in terms of name-value pairs. It will be merged with [[errorOptions]].
+ * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
+ * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ *
+ * The following options are specially handled:
+ *
+ * - tag: this specifies the tag name. If not set, "div" will be used.
+ *
+ * @return static the field object itself
+ */
+ public function error($options = [])
+ {
+ $options = array_merge($this->errorOptions, $options);
+ $this->parts['{error}'] = Html::error($this->model, $this->attribute, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders the hint tag.
+ * @param string $content the hint content. It will NOT be HTML-encoded.
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
+ * the attributes of the hint tag. The values will be HTML-encoded using [[Html::encode()]].
+ *
+ * The following options are specially handled:
+ *
+ * - tag: this specifies the tag name. If not set, "div" will be used.
+ *
+ * @return static the field object itself
+ */
+ public function hint($content, $options = [])
+ {
+ $options = array_merge($this->hintOptions, $options);
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ $this->parts['{hint}'] = Html::tag($tag, $content, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders an input tag.
+ * @param string $type the input type (e.g. 'text', 'password')
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
+ * @return static the field object itself
+ */
+ public function input($type, $options = [])
+ {
+ $options = array_merge($this->inputOptions, $options);
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a text input.
+ * This method will generate the "name" and "value" tag attributes automatically for the model attribute
+ * unless they are explicitly specified in `$options`.
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
+ * @return static the field object itself
+ */
+ public function textInput($options = [])
+ {
+ $options = array_merge($this->inputOptions, $options);
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a password input.
+ * This method will generate the "name" and "value" tag attributes automatically for the model attribute
+ * unless they are explicitly specified in `$options`.
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
+ * @return static the field object itself
+ */
+ public function passwordInput($options = [])
+ {
+ $options = array_merge($this->inputOptions, $options);
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a file input.
+ * This method will generate the "name" and "value" tag attributes automatically for the model attribute
+ * unless they are explicitly specified in `$options`.
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
+ * @return static the field object itself
+ */
+ public function fileInput($options = [])
+ {
+ // https://github.com/yiisoft/yii2/pull/795
+ if ($this->inputOptions !== ['class' => 'form-control']) {
+ $options = array_merge($this->inputOptions, $options);
+ }
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a text area.
+ * The model attribute value will be used as the content in the textarea.
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
+ * @return static the field object itself
+ */
+ public function textarea($options = [])
+ {
+ $options = array_merge($this->inputOptions, $options);
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a radio button.
+ * This method will generate the "checked" tag attribute according to the model attribute value.
+ * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
+ *
+ * - uncheck: string, the value associated with the uncheck state of the radio button. If not set,
+ * it will take the default value '0'. This method will render a hidden input so that if the radio button
+ * is not checked and is submitted, the value of this attribute will still be submitted to the server
+ * via the hidden input.
+ * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass
+ * in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] it to prevent XSS attacks.
+ * When this option is specified, the radio button will be enclosed by a label tag.
+ * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
+ *
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ * @param boolean $enclosedByLabel whether to enclose the radio within the label.
+ * If true, the method will still use [[template]] to layout the checkbox and the error message
+ * except that the radio is enclosed by the label tag.
+ * @return static the field object itself
+ */
+ public function radio($options = [], $enclosedByLabel = true)
+ {
+ if ($enclosedByLabel) {
+ if (!isset($options['label'])) {
+ $attribute = Html::getAttributeName($this->attribute);
+ $options['label'] = Html::encode($this->model->getAttributeLabel($attribute));
+ }
+ $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
+ $this->parts['{label}'] = '';
+ } else {
+ $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
+ }
+ $this->adjustLabelFor($options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a checkbox.
+ * This method will generate the "checked" tag attribute according to the model attribute value.
+ * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
+ *
+ * - uncheck: string, the value associated with the uncheck state of the radio button. If not set,
+ * it will take the default value '0'. This method will render a hidden input so that if the radio button
+ * is not checked and is submitted, the value of this attribute will still be submitted to the server
+ * via the hidden input.
+ * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass
+ * in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] it to prevent XSS attacks.
+ * When this option is specified, the checkbox will be enclosed by a label tag.
+ * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
+ *
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ * @param boolean $enclosedByLabel whether to enclose the checkbox within the label.
+ * If true, the method will still use [[template]] to layout the checkbox and the error message
+ * except that the checkbox is enclosed by the label tag.
+ * @return static the field object itself
+ */
+ public function checkbox($options = [], $enclosedByLabel = true)
+ {
+ if ($enclosedByLabel) {
+ if (!isset($options['label'])) {
+ $attribute = Html::getAttributeName($this->attribute);
+ $options['label'] = Html::encode($this->model->getAttributeLabel($attribute));
+ }
+ $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
+ $this->parts['{label}'] = '';
+ } else {
+ $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
+ }
+ $this->adjustLabelFor($options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a drop-down list.
+ * The selection of the drop-down list is taken from the value of the model attribute.
+ * @param array $items the option data items. The array keys are option values, and the array values
+ * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
+ * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
+ * If you have a list of data models, you may convert them into the format described above using
+ * [[ArrayHelper::map()]].
+ *
+ * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+ * the labels will also be HTML-encoded.
+ * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
+ *
+ * - prompt: string, a prompt text to be displayed as the first option;
+ * - options: array, the attributes for the select option tags. The array keys must be valid option values,
+ * and the array values are the extra attributes for the corresponding option tags. For example,
+ *
+ * ~~~
+ * [
+ * 'value1' => ['disabled' => true],
+ * 'value2' => ['label' => 'value 2'],
+ * ];
+ * ~~~
+ *
+ * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
+ * except that the array keys represent the optgroup labels specified in $items.
+ *
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ *
+ * @return static the field object itself
+ */
+ public function dropDownList($items, $options = [])
+ {
+ $options = array_merge($this->inputOptions, $options);
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a list box.
+ * The selection of the list box is taken from the value of the model attribute.
+ * @param array $items the option data items. The array keys are option values, and the array values
+ * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
+ * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
+ * If you have a list of data models, you may convert them into the format described above using
+ * [[\yii\helpers\ArrayHelper::map()]].
+ *
+ * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+ * the labels will also be HTML-encoded.
+ * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
+ *
+ * - prompt: string, a prompt text to be displayed as the first option;
+ * - options: array, the attributes for the select option tags. The array keys must be valid option values,
+ * and the array values are the extra attributes for the corresponding option tags. For example,
+ *
+ * ~~~
+ * [
+ * 'value1' => ['disabled' => true],
+ * 'value2' => ['label' => 'value 2'],
+ * ];
+ * ~~~
+ *
+ * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
+ * except that the array keys represent the optgroup labels specified in $items.
+ * - unselect: string, the value that will be submitted when no option is selected.
+ * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple
+ * mode, we can still obtain the posted unselect value.
+ *
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ *
+ * @return static the field object itself
+ */
+ public function listBox($items, $options = [])
+ {
+ $options = array_merge($this->inputOptions, $options);
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a list of checkboxes.
+ * A checkbox list allows multiple selection, like [[listBox()]].
+ * As a result, the corresponding submitted value is an array.
+ * The selection of the checkbox list is taken from the value of the model attribute.
+ * @param array $items the data item used to generate the checkboxes.
+ * The array values are the labels, while the array keys are the corresponding checkbox values.
+ * Note that the labels will NOT be HTML-encoded, while the values will.
+ * @param array $options options (name => config) for the checkbox list. The following options are specially handled:
+ *
+ * - unselect: string, the value that should be submitted when none of the checkboxes is selected.
+ * By setting this option, a hidden input will be generated.
+ * - separator: string, the HTML code that separates items.
+ * - item: callable, a callback that can be used to customize the generation of the HTML code
+ * corresponding to a single item in $items. The signature of this callback must be:
+ *
+ * ~~~
+ * function ($index, $label, $name, $checked, $value)
+ * ~~~
+ *
+ * where $index is the zero-based index of the checkbox in the whole list; $label
+ * is the label for the checkbox; and $name, $value and $checked represent the name,
+ * value and the checked status of the checkbox input.
+ * @return static the field object itself
+ */
+ public function checkboxList($items, $options = [])
+ {
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a list of radio buttons.
+ * A radio button list is like a checkbox list, except that it only allows single selection.
+ * The selection of the radio buttons is taken from the value of the model attribute.
+ * @param array $items the data item used to generate the radio buttons.
+ * The array keys are the labels, while the array values are the corresponding radio button values.
+ * Note that the labels will NOT be HTML-encoded, while the values will.
+ * @param array $options options (name => config) for the radio button list. The following options are specially handled:
+ *
+ * - unselect: string, the value that should be submitted when none of the radio buttons is selected.
+ * By setting this option, a hidden input will be generated.
+ * - separator: string, the HTML code that separates items.
+ * - item: callable, a callback that can be used to customize the generation of the HTML code
+ * corresponding to a single item in $items. The signature of this callback must be:
+ *
+ * ~~~
+ * function ($index, $label, $name, $checked, $value)
+ * ~~~
+ *
+ * where $index is the zero-based index of the radio button in the whole list; $label
+ * is the label for the radio button; and $name, $value and $checked represent the name,
+ * value and the checked status of the radio button input.
+ * @return static the field object itself
+ */
+ public function radioList($items, $options = [])
+ {
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options);
+
+ return $this;
+ }
+
+ /**
+ * Renders a widget as the input of the field.
+ *
+ * Note that the widget must have both `model` and `attribute` properties. They will
+ * be initialized with [[model]] and [[attribute]] of this field, respectively.
+ *
+ * If you want to use a widget that does not have `model` and `attribute` properties,
+ * please use [[render()]] instead.
+ *
+ * @param string $class the widget class name
+ * @param array $config name-value pairs that will be used to initialize the widget
+ * @return static the field object itself
+ */
+ public function widget($class, $config = [])
+ {
+ /** @var \yii\base\Widget $class */
+ $config['model'] = $this->model;
+ $config['attribute'] = $this->attribute;
+ $config['view'] = $this->form->getView();
+ $this->parts['{input}'] = $class::widget($config);
+
+ return $this;
+ }
+
+ /**
+ * Adjusts the "for" attribute for the label based on the input options.
+ * @param array $options the input options
+ */
+ protected function adjustLabelFor($options)
+ {
+ if (isset($options['id']) && !isset($this->labelOptions['for'])) {
+ $this->labelOptions['for'] = $options['id'];
+ }
+ }
+
+ /**
+ * Returns the JS options for the field.
+ * @return array the JS options
+ */
+ protected function getClientOptions()
+ {
+ $attribute = Html::getAttributeName($this->attribute);
+ if (!in_array($attribute, $this->model->activeAttributes(), true)) {
+ return [];
+ }
+
+ $options = [];
+
+ $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
+ if ($enableClientValidation) {
+ $validators = [];
+ foreach ($this->model->getActiveValidators($attribute) as $validator) {
+ /** @var \yii\validators\Validator $validator */
+ $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView());
+ if ($validator->enableClientValidation && $js != '') {
+ $validators[] = $js;
+ }
+ }
+ if (!empty($validators)) {
+ $options['validate'] = new JsExpression("function (attribute, value, messages) {" . implode('', $validators) . '}');
+ }
+ }
+
+ $enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
+ if ($enableAjaxValidation) {
+ $options['enableAjaxValidation'] = 1;
+ }
+
+ if ($enableClientValidation && !empty($options['validate']) || $enableAjaxValidation) {
+ $inputID = Html::getInputId($this->model, $this->attribute);
+ $options['name'] = $inputID;
+ foreach (['validateOnChange', 'validateOnType', 'validationDelay'] as $name) {
+ $options[$name] = $this->$name === null ? $this->form->$name : $this->$name;
+ }
+ $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID";
+ $options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID";
+ if (isset($this->errorOptions['class'])) {
+ $options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY));
+ } else {
+ $options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span';
+ }
+
+ return $options;
+ } else {
+ return [];
+ }
+ }
}
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
index 3a04e62af6a..06ead69ce23 100644
--- a/framework/widgets/ActiveForm.php
+++ b/framework/widgets/ActiveForm.php
@@ -23,340 +23,345 @@
*/
class ActiveForm extends Widget
{
- /**
- * @param array|string $action the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]].
- */
- public $action = '';
- /**
- * @var string the form submission method. This should be either 'post' or 'get'.
- * Defaults to 'post'.
- */
- public $method = 'post';
- /**
- * @var array the HTML attributes (name-value pairs) for the form tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var array the default configuration used by [[field()]] when creating a new field object.
- */
- public $fieldConfig;
- /**
- * @var string the default CSS class for the error summary container.
- * @see errorSummary()
- */
- public $errorSummaryCssClass = 'error-summary';
- /**
- * @var string the CSS class that is added to a field container when the associated attribute is required.
- */
- public $requiredCssClass = 'required';
- /**
- * @var string the CSS class that is added to a field container when the associated attribute has validation error.
- */
- public $errorCssClass = 'has-error';
- /**
- * @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
- */
- public $successCssClass = 'has-success';
- /**
- * @var string the CSS class that is added to a field container when the associated attribute is being validated.
- */
- public $validatingCssClass = 'validating';
- /**
- * @var boolean whether to enable client-side data validation.
- * If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
- */
- public $enableClientValidation = true;
- /**
- * @var boolean whether to enable AJAX-based data validation.
- * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field.
- */
- public $enableAjaxValidation = false;
- /**
- * @var array|string the URL for performing AJAX-based validation. This property will be processed by
- * [[Url::to()]]. Please refer to [[Url::to()]] for more details on how to configure this property.
- * If this property is not set, it will take the value of the form's action attribute.
- */
- public $validationUrl;
- /**
- * @var boolean whether to perform validation when the form is submitted.
- */
- public $validateOnSubmit = true;
- /**
- * @var boolean whether to perform validation when an input field loses focus and its value is found changed.
- * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field.
- */
- public $validateOnChange = true;
- /**
- * @var boolean whether to perform validation while the user is typing in an input field.
- * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field.
- * @see validationDelay
- */
- public $validateOnType = false;
- /**
- * @var integer number of milliseconds that the validation should be delayed when an input field
- * is changed or the user types in the field.
- * If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field.
- */
- public $validationDelay = 200;
- /**
- * @var string the name of the GET parameter indicating the validation request is an AJAX request.
- */
- public $ajaxParam = 'ajax';
- /**
- * @var string the type of data that you're expecting back from the server.
- */
- public $ajaxDataType = 'json';
- /**
- * @var string|JsExpression a JS callback that will be called when the form is being submitted.
- * The signature of the callback should be:
- *
- * ~~~
- * function ($form) {
- * ...return false to cancel submission...
- * }
- * ~~~
- */
- public $beforeSubmit;
- /**
- * @var string|JsExpression a JS callback that is called before validating an attribute.
- * The signature of the callback should be:
- *
- * ~~~
- * function ($form, attribute, messages) {
- * ...return false to cancel the validation...
- * }
- * ~~~
- */
- public $beforeValidate;
- /**
- * @var string|JsExpression a JS callback that is called after validating an attribute.
- * The signature of the callback should be:
- *
- * ~~~
- * function ($form, attribute, messages) {
- * }
- * ~~~
- */
- public $afterValidate;
- /**
- * @var array the client validation options for individual attributes. Each element of the array
- * represents the validation options for a particular attribute.
- * @internal
- */
- public $attributes = [];
+ /**
+ * @param array|string $action the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]].
+ */
+ public $action = '';
+ /**
+ * @var string the form submission method. This should be either 'post' or 'get'.
+ * Defaults to 'post'.
+ */
+ public $method = 'post';
+ /**
+ * @var array the HTML attributes (name-value pairs) for the form tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var array the default configuration used by [[field()]] when creating a new field object.
+ */
+ public $fieldConfig;
+ /**
+ * @var string the default CSS class for the error summary container.
+ * @see errorSummary()
+ */
+ public $errorSummaryCssClass = 'error-summary';
+ /**
+ * @var string the CSS class that is added to a field container when the associated attribute is required.
+ */
+ public $requiredCssClass = 'required';
+ /**
+ * @var string the CSS class that is added to a field container when the associated attribute has validation error.
+ */
+ public $errorCssClass = 'has-error';
+ /**
+ * @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
+ */
+ public $successCssClass = 'has-success';
+ /**
+ * @var string the CSS class that is added to a field container when the associated attribute is being validated.
+ */
+ public $validatingCssClass = 'validating';
+ /**
+ * @var boolean whether to enable client-side data validation.
+ * If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
+ */
+ public $enableClientValidation = true;
+ /**
+ * @var boolean whether to enable AJAX-based data validation.
+ * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field.
+ */
+ public $enableAjaxValidation = false;
+ /**
+ * @var array|string the URL for performing AJAX-based validation. This property will be processed by
+ * [[Url::to()]]. Please refer to [[Url::to()]] for more details on how to configure this property.
+ * If this property is not set, it will take the value of the form's action attribute.
+ */
+ public $validationUrl;
+ /**
+ * @var boolean whether to perform validation when the form is submitted.
+ */
+ public $validateOnSubmit = true;
+ /**
+ * @var boolean whether to perform validation when an input field loses focus and its value is found changed.
+ * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field.
+ */
+ public $validateOnChange = true;
+ /**
+ * @var boolean whether to perform validation while the user is typing in an input field.
+ * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field.
+ * @see validationDelay
+ */
+ public $validateOnType = false;
+ /**
+ * @var integer number of milliseconds that the validation should be delayed when an input field
+ * is changed or the user types in the field.
+ * If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field.
+ */
+ public $validationDelay = 200;
+ /**
+ * @var string the name of the GET parameter indicating the validation request is an AJAX request.
+ */
+ public $ajaxParam = 'ajax';
+ /**
+ * @var string the type of data that you're expecting back from the server.
+ */
+ public $ajaxDataType = 'json';
+ /**
+ * @var string|JsExpression a JS callback that will be called when the form is being submitted.
+ * The signature of the callback should be:
+ *
+ * ~~~
+ * function ($form) {
+ * ...return false to cancel submission...
+ * }
+ * ~~~
+ */
+ public $beforeSubmit;
+ /**
+ * @var string|JsExpression a JS callback that is called before validating an attribute.
+ * The signature of the callback should be:
+ *
+ * ~~~
+ * function ($form, attribute, messages) {
+ * ...return false to cancel the validation...
+ * }
+ * ~~~
+ */
+ public $beforeValidate;
+ /**
+ * @var string|JsExpression a JS callback that is called after validating an attribute.
+ * The signature of the callback should be:
+ *
+ * ~~~
+ * function ($form, attribute, messages) {
+ * }
+ * ~~~
+ */
+ public $afterValidate;
+ /**
+ * @var array the client validation options for individual attributes. Each element of the array
+ * represents the validation options for a particular attribute.
+ * @internal
+ */
+ public $attributes = [];
- /**
- * Initializes the widget.
- * This renders the form open tag.
- */
- public function init()
- {
- if (!isset($this->options['id'])) {
- $this->options['id'] = $this->getId();
- }
- if (!isset($this->fieldConfig['class'])) {
- $this->fieldConfig['class'] = ActiveField::className();
- }
- echo Html::beginForm($this->action, $this->method, $this->options);
- }
+ /**
+ * Initializes the widget.
+ * This renders the form open tag.
+ */
+ public function init()
+ {
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->getId();
+ }
+ if (!isset($this->fieldConfig['class'])) {
+ $this->fieldConfig['class'] = ActiveField::className();
+ }
+ echo Html::beginForm($this->action, $this->method, $this->options);
+ }
- /**
- * Runs the widget.
- * This registers the necessary javascript code and renders the form close tag.
- */
- public function run()
- {
- if (!empty($this->attributes)) {
- $id = $this->options['id'];
- $options = Json::encode($this->getClientOptions());
- $attributes = Json::encode($this->attributes);
- $view = $this->getView();
- ActiveFormAsset::register($view);
- $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
- }
- echo Html::endForm();
- }
+ /**
+ * Runs the widget.
+ * This registers the necessary javascript code and renders the form close tag.
+ */
+ public function run()
+ {
+ if (!empty($this->attributes)) {
+ $id = $this->options['id'];
+ $options = Json::encode($this->getClientOptions());
+ $attributes = Json::encode($this->attributes);
+ $view = $this->getView();
+ ActiveFormAsset::register($view);
+ $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
+ }
+ echo Html::endForm();
+ }
- /**
- * Returns the options for the form JS widget.
- * @return array the options
- */
- protected function getClientOptions()
- {
- $options = [
- 'errorSummary' => '.' . $this->errorSummaryCssClass,
- 'validateOnSubmit' => $this->validateOnSubmit,
- 'errorCssClass' => $this->errorCssClass,
- 'successCssClass' => $this->successCssClass,
- 'validatingCssClass' => $this->validatingCssClass,
- 'ajaxParam' => $this->ajaxParam,
- 'ajaxDataType' => $this->ajaxDataType,
- ];
- if ($this->validationUrl !== null) {
- $options['validationUrl'] = Url::to($this->validationUrl);
- }
- foreach (['beforeSubmit', 'beforeValidate', 'afterValidate'] as $name) {
- if (($value = $this->$name) !== null) {
- $options[$name] = $value instanceof JsExpression ? $value : new JsExpression($value);
- }
- }
- return $options;
- }
+ /**
+ * Returns the options for the form JS widget.
+ * @return array the options
+ */
+ protected function getClientOptions()
+ {
+ $options = [
+ 'errorSummary' => '.' . $this->errorSummaryCssClass,
+ 'validateOnSubmit' => $this->validateOnSubmit,
+ 'errorCssClass' => $this->errorCssClass,
+ 'successCssClass' => $this->successCssClass,
+ 'validatingCssClass' => $this->validatingCssClass,
+ 'ajaxParam' => $this->ajaxParam,
+ 'ajaxDataType' => $this->ajaxDataType,
+ ];
+ if ($this->validationUrl !== null) {
+ $options['validationUrl'] = Url::to($this->validationUrl);
+ }
+ foreach (['beforeSubmit', 'beforeValidate', 'afterValidate'] as $name) {
+ if (($value = $this->$name) !== null) {
+ $options[$name] = $value instanceof JsExpression ? $value : new JsExpression($value);
+ }
+ }
- /**
- * Generates a summary of the validation errors.
- * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
- * @param Model|Model[] $models the model(s) associated with this form
- * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
- *
- * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used.
- * - footer: string, the footer HTML for the error summary.
- *
- * The rest of the options will be rendered as the attributes of the container tag. The values will
- * be HTML-encoded using [[\yii\helpers\Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
- * @return string the generated error summary
- */
- public function errorSummary($models, $options = [])
- {
- if (!is_array($models)) {
- $models = [$models];
- }
+ return $options;
+ }
- $lines = [];
- foreach ($models as $model) {
- /** @var Model $model */
- foreach ($model->getFirstErrors() as $error) {
- $lines[] = Html::encode($error);
- }
- }
+ /**
+ * Generates a summary of the validation errors.
+ * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
+ * @param Model|Model[] $models the model(s) associated with this form
+ * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
+ *
+ * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used.
+ * - footer: string, the footer HTML for the error summary.
+ *
+ * The rest of the options will be rendered as the attributes of the container tag. The values will
+ * be HTML-encoded using [[\yii\helpers\Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ * @return string the generated error summary
+ */
+ public function errorSummary($models, $options = [])
+ {
+ if (!is_array($models)) {
+ $models = [$models];
+ }
- $header = isset($options['header']) ? $options['header'] : '
' . Yii::t('yii', 'Please fix the following errors:') . '
";
- $options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
- return Html::tag('div', $header . $content . $footer, $options);
- }
- }
+ if (!isset($options['class'])) {
+ $options['class'] = $this->errorSummaryCssClass;
+ } else {
+ $options['class'] .= ' ' . $this->errorSummaryCssClass;
+ }
- /**
- * Generates a form field.
- * A form field is associated with a model and an attribute. It contains a label, an input and an error message
- * and use them to interact with end users to collect their inputs for the attribute.
- * @param Model $model the data model
- * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
- * about attribute expression.
- * @param array $options the additional configurations for the field object
- * @return ActiveField the created ActiveField object
- * @see fieldConfig
- */
- public function field($model, $attribute, $options = [])
- {
- return Yii::createObject(array_merge($this->fieldConfig, $options, [
- 'model' => $model,
- 'attribute' => $attribute,
- 'form' => $this,
- ]));
- }
+ if (!empty($lines)) {
+ $content = "
" . implode("
\n
", $lines) . "
";
- /**
- * Validates one or several models and returns an error message array indexed by the attribute IDs.
- * This is a helper method that simplifies the way of writing AJAX validation code.
- *
- * For example, you may use the following code in a controller action to respond
- * to an AJAX validation request:
- *
- * ~~~
- * $model = new Post;
- * $model->load($_POST);
- * if (Yii::$app->request->isAjax) {
- * Yii::$app->response->format = Response::FORMAT_JSON;
- * return ActiveForm::validate($model);
- * }
- * // ... respond to non-AJAX request ...
- * ~~~
- *
- * To validate multiple models, simply pass each model as a parameter to this method, like
- * the following:
- *
- * ~~~
- * ActiveForm::validate($model1, $model2, ...);
- * ~~~
- *
- * @param Model $model the model to be validated
- * @param mixed $attributes list of attributes that should be validated.
- * If this parameter is empty, it means any attribute listed in the applicable
- * validation rules should be validated.
- *
- * When this method is used to validate multiple models, this parameter will be interpreted
- * as a model.
- *
- * @return array the error message array indexed by the attribute IDs.
- */
- public static function validate($model, $attributes = null)
- {
- $result = [];
- if ($attributes instanceof Model) {
- // validating multiple models
- $models = func_get_args();
- $attributes = null;
- } else {
- $models = [$model];
- }
- /** @var Model $model */
- foreach ($models as $model) {
- $model->validate($attributes);
- foreach ($model->getErrors() as $attribute => $errors) {
- $result[Html::getInputId($model, $attribute)] = $errors;
- }
- }
- return $result;
- }
+ return Html::tag('div', $header . $content . $footer, $options);
+ } else {
+ $content = "
";
+ $options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
- /**
- * Validates an array of model instances and returns an error message array indexed by the attribute IDs.
- * This is a helper method that simplifies the way of writing AJAX validation code for tabular input.
- *
- * For example, you may use the following code in a controller action to respond
- * to an AJAX validation request:
- *
- * ~~~
- * // ... load $models ...
- * if (Yii::$app->request->isAjax) {
- * Yii::$app->response->format = Response::FORMAT_JSON;
- * return ActiveForm::validateMultiple($models);
- * }
- * // ... respond to non-AJAX request ...
- * ~~~
- *
- * @param array $models an array of models to be validated.
- * @param mixed $attributes list of attributes that should be validated.
- * If this parameter is empty, it means any attribute listed in the applicable
- * validation rules should be validated.
- * @return array the error message array indexed by the attribute IDs.
- */
- public static function validateMultiple($models, $attributes = null)
- {
- $result = [];
- /** @var Model $model */
- foreach ($models as $i => $model) {
- $model->validate($attributes);
- foreach ($model->getErrors() as $attribute => $errors) {
- $result[Html::getInputId($model, "[$i]" . $attribute)] = $errors;
- }
- }
- return $result;
- }
+ return Html::tag('div', $header . $content . $footer, $options);
+ }
+ }
+
+ /**
+ * Generates a form field.
+ * A form field is associated with a model and an attribute. It contains a label, an input and an error message
+ * and use them to interact with end users to collect their inputs for the attribute.
+ * @param Model $model the data model
+ * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
+ * about attribute expression.
+ * @param array $options the additional configurations for the field object
+ * @return ActiveField the created ActiveField object
+ * @see fieldConfig
+ */
+ public function field($model, $attribute, $options = [])
+ {
+ return Yii::createObject(array_merge($this->fieldConfig, $options, [
+ 'model' => $model,
+ 'attribute' => $attribute,
+ 'form' => $this,
+ ]));
+ }
+
+ /**
+ * Validates one or several models and returns an error message array indexed by the attribute IDs.
+ * This is a helper method that simplifies the way of writing AJAX validation code.
+ *
+ * For example, you may use the following code in a controller action to respond
+ * to an AJAX validation request:
+ *
+ * ~~~
+ * $model = new Post;
+ * $model->load($_POST);
+ * if (Yii::$app->request->isAjax) {
+ * Yii::$app->response->format = Response::FORMAT_JSON;
+ * return ActiveForm::validate($model);
+ * }
+ * // ... respond to non-AJAX request ...
+ * ~~~
+ *
+ * To validate multiple models, simply pass each model as a parameter to this method, like
+ * the following:
+ *
+ * ~~~
+ * ActiveForm::validate($model1, $model2, ...);
+ * ~~~
+ *
+ * @param Model $model the model to be validated
+ * @param mixed $attributes list of attributes that should be validated.
+ * If this parameter is empty, it means any attribute listed in the applicable
+ * validation rules should be validated.
+ *
+ * When this method is used to validate multiple models, this parameter will be interpreted
+ * as a model.
+ *
+ * @return array the error message array indexed by the attribute IDs.
+ */
+ public static function validate($model, $attributes = null)
+ {
+ $result = [];
+ if ($attributes instanceof Model) {
+ // validating multiple models
+ $models = func_get_args();
+ $attributes = null;
+ } else {
+ $models = [$model];
+ }
+ /** @var Model $model */
+ foreach ($models as $model) {
+ $model->validate($attributes);
+ foreach ($model->getErrors() as $attribute => $errors) {
+ $result[Html::getInputId($model, $attribute)] = $errors;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Validates an array of model instances and returns an error message array indexed by the attribute IDs.
+ * This is a helper method that simplifies the way of writing AJAX validation code for tabular input.
+ *
+ * For example, you may use the following code in a controller action to respond
+ * to an AJAX validation request:
+ *
+ * ~~~
+ * // ... load $models ...
+ * if (Yii::$app->request->isAjax) {
+ * Yii::$app->response->format = Response::FORMAT_JSON;
+ * return ActiveForm::validateMultiple($models);
+ * }
+ * // ... respond to non-AJAX request ...
+ * ~~~
+ *
+ * @param array $models an array of models to be validated.
+ * @param mixed $attributes list of attributes that should be validated.
+ * If this parameter is empty, it means any attribute listed in the applicable
+ * validation rules should be validated.
+ * @return array the error message array indexed by the attribute IDs.
+ */
+ public static function validateMultiple($models, $attributes = null)
+ {
+ $result = [];
+ /** @var Model $model */
+ foreach ($models as $i => $model) {
+ $model->validate($attributes);
+ foreach ($model->getErrors() as $attribute => $errors) {
+ $result[Html::getInputId($model, "[$i]" . $attribute)] = $errors;
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/framework/widgets/ActiveFormAsset.php b/framework/widgets/ActiveFormAsset.php
index 94b00e5143e..971606318a1 100644
--- a/framework/widgets/ActiveFormAsset.php
+++ b/framework/widgets/ActiveFormAsset.php
@@ -15,11 +15,11 @@
*/
class ActiveFormAsset extends AssetBundle
{
- public $sourcePath = '@yii/assets';
- public $js = [
- 'yii.activeForm.js',
- ];
- public $depends = [
- 'yii\web\YiiAsset',
- ];
+ public $sourcePath = '@yii/assets';
+ public $js = [
+ 'yii.activeForm.js',
+ ];
+ public $depends = [
+ 'yii\web\YiiAsset',
+ ];
}
diff --git a/framework/widgets/BaseListView.php b/framework/widgets/BaseListView.php
index 8fddf4f69c7..14398b90ceb 100644
--- a/framework/widgets/BaseListView.php
+++ b/framework/widgets/BaseListView.php
@@ -19,216 +19,219 @@
*/
abstract class BaseListView extends Widget
{
- /**
- * @var array the HTML attributes for the container tag of the list view.
- * The "tag" element specifies the tag name of the container element and defaults to "div".
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var \yii\data\DataProviderInterface the data provider for the view. This property is required.
- */
- public $dataProvider;
- /**
- * @var array the configuration for the pager widget. By default, [[LinkPager]] will be
- * used to render the pager. You can use a different widget class by configuring the "class" element.
- */
- public $pager = [];
- /**
- * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be
- * used to render the sorter. You can use a different widget class by configuring the "class" element.
- */
- public $sorter = [];
- /**
- * @var string the HTML content to be displayed as the summary of the list view.
- * If you do not want to show the summary, you may set it with an empty string.
- *
- * The following tokens will be replaced with the corresponding values:
- *
- * - `{begin}`: the starting row number (1-based) currently being displayed
- * - `{end}`: the ending row number (1-based) currently being displayed
- * - `{count}`: the number of rows currently being displayed
- * - `{totalCount}`: the total number of rows available
- * - `{page}`: the page number (1-based) current being displayed
- * - `{pageCount}`: the number of pages available
- */
- public $summary;
- /**
- * @var boolean whether to show the list view if [[dataProvider]] returns no data.
- */
- public $showOnEmpty = false;
- /**
- * @var string the HTML content to be displayed when [[dataProvider]] does not have any data.
- */
- public $emptyText;
- /**
- * @var string the layout that determines how different sections of the list view should be organized.
- * The following tokens will be replaced with the corresponding section contents:
- *
- * - `{summary}`: the summary section. See [[renderSummary()]].
- * - `{items}`: the list items. See [[renderItems()]].
- * - `{sorter}`: the sorter. See [[renderSorter()]].
- * - `{pager}`: the pager. See [[renderPager()]].
- */
- public $layout = "{summary}\n{items}\n{pager}";
+ /**
+ * @var array the HTML attributes for the container tag of the list view.
+ * The "tag" element specifies the tag name of the container element and defaults to "div".
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * @var \yii\data\DataProviderInterface the data provider for the view. This property is required.
+ */
+ public $dataProvider;
+ /**
+ * @var array the configuration for the pager widget. By default, [[LinkPager]] will be
+ * used to render the pager. You can use a different widget class by configuring the "class" element.
+ */
+ public $pager = [];
+ /**
+ * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be
+ * used to render the sorter. You can use a different widget class by configuring the "class" element.
+ */
+ public $sorter = [];
+ /**
+ * @var string the HTML content to be displayed as the summary of the list view.
+ * If you do not want to show the summary, you may set it with an empty string.
+ *
+ * The following tokens will be replaced with the corresponding values:
+ *
+ * - `{begin}`: the starting row number (1-based) currently being displayed
+ * - `{end}`: the ending row number (1-based) currently being displayed
+ * - `{count}`: the number of rows currently being displayed
+ * - `{totalCount}`: the total number of rows available
+ * - `{page}`: the page number (1-based) current being displayed
+ * - `{pageCount}`: the number of pages available
+ */
+ public $summary;
+ /**
+ * @var boolean whether to show the list view if [[dataProvider]] returns no data.
+ */
+ public $showOnEmpty = false;
+ /**
+ * @var string the HTML content to be displayed when [[dataProvider]] does not have any data.
+ */
+ public $emptyText;
+ /**
+ * @var string the layout that determines how different sections of the list view should be organized.
+ * The following tokens will be replaced with the corresponding section contents:
+ *
+ * - `{summary}`: the summary section. See [[renderSummary()]].
+ * - `{items}`: the list items. See [[renderItems()]].
+ * - `{sorter}`: the sorter. See [[renderSorter()]].
+ * - `{pager}`: the pager. See [[renderPager()]].
+ */
+ public $layout = "{summary}\n{items}\n{pager}";
+ /**
+ * Renders the data models.
+ * @return string the rendering result.
+ */
+ abstract public function renderItems();
- /**
- * Renders the data models.
- * @return string the rendering result.
- */
- abstract public function renderItems();
+ /**
+ * Initializes the view.
+ */
+ public function init()
+ {
+ if ($this->dataProvider === null) {
+ throw new InvalidConfigException('The "dataProvider" property must be set.');
+ }
+ if ($this->emptyText === null) {
+ $this->emptyText = Yii::t('yii', 'No results found.');
+ }
+ $this->dataProvider->prepare();
+ }
- /**
- * Initializes the view.
- */
- public function init()
- {
- if ($this->dataProvider === null) {
- throw new InvalidConfigException('The "dataProvider" property must be set.');
- }
- if ($this->emptyText === null) {
- $this->emptyText = Yii::t('yii', 'No results found.');
- }
- $this->dataProvider->prepare();
- }
+ /**
+ * Runs the widget.
+ */
+ public function run()
+ {
+ if ($this->dataProvider->getCount() > 0 || $this->showOnEmpty) {
+ $content = preg_replace_callback("/{\\w+}/", function ($matches) {
+ $content = $this->renderSection($matches[0]);
- /**
- * Runs the widget.
- */
- public function run()
- {
- if ($this->dataProvider->getCount() > 0 || $this->showOnEmpty) {
- $content = preg_replace_callback("/{\\w+}/", function ($matches) {
- $content = $this->renderSection($matches[0]);
- return $content === false ? $matches[0] : $content;
- }, $this->layout);
- } else {
- $content = $this->renderEmpty();
- }
- $tag = ArrayHelper::remove($this->options, 'tag', 'div');
- echo Html::tag($tag, $content, $this->options);
- }
+ return $content === false ? $matches[0] : $content;
+ }, $this->layout);
+ } else {
+ $content = $this->renderEmpty();
+ }
+ $tag = ArrayHelper::remove($this->options, 'tag', 'div');
+ echo Html::tag($tag, $content, $this->options);
+ }
- /**
- * Renders a section of the specified name.
- * If the named section is not supported, false will be returned.
- * @param string $name the section name, e.g., `{summary}`, `{items}`.
- * @return string|boolean the rendering result of the section, or false if the named section is not supported.
- */
- public function renderSection($name)
- {
- switch ($name) {
- case '{summary}':
- return $this->renderSummary();
- case '{items}':
- return $this->renderItems();
- case '{pager}':
- return $this->renderPager();
- case '{sorter}':
- return $this->renderSorter();
- default:
- return false;
- }
- }
+ /**
+ * Renders a section of the specified name.
+ * If the named section is not supported, false will be returned.
+ * @param string $name the section name, e.g., `{summary}`, `{items}`.
+ * @return string|boolean the rendering result of the section, or false if the named section is not supported.
+ */
+ public function renderSection($name)
+ {
+ switch ($name) {
+ case '{summary}':
+ return $this->renderSummary();
+ case '{items}':
+ return $this->renderItems();
+ case '{pager}':
+ return $this->renderPager();
+ case '{sorter}':
+ return $this->renderSorter();
+ default:
+ return false;
+ }
+ }
- /**
- * Renders the HTML content indicating that the list view has no data.
- * @return string the rendering result
- * @see emptyText
- */
- public function renderEmpty()
- {
- return '
';
- }
+ /**
+ * Renders the HTML content indicating that the list view has no data.
+ * @return string the rendering result
+ * @see emptyText
+ */
+ public function renderEmpty()
+ {
+ return '
';
+ }
+ }
- /**
- * Renders the pager.
- * @return string the rendering result
- */
- public function renderPager()
- {
- $pagination = $this->dataProvider->getPagination();
- if ($pagination === false || $this->dataProvider->getCount() <= 0) {
- return '';
- }
- /** @var LinkPager $class */
- $class = ArrayHelper::remove($this->pager, 'class', LinkPager::className());
- $pager = $this->pager;
- $pager['pagination'] = $pagination;
- $pager['view'] = $this->getView();
- return $class::widget($pager);
- }
+ return Yii::$app->getI18n()->format($summaryContent, [
+ 'begin' => $begin,
+ 'end' => $end,
+ 'count' => $count,
+ 'totalCount' => $totalCount,
+ 'page' => $page,
+ 'pageCount' => $pageCount,
+ ], Yii::$app->language);
+ }
- /**
- * Renders the sorter.
- * @return string the rendering result
- */
- public function renderSorter()
- {
- $sort = $this->dataProvider->getSort();
- if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) {
- return '';
- }
- /** @var LinkSorter $class */
- $class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className());
- $sorter = $this->sorter;
- $sorter['sort'] = $sort;
- $sorter['view'] = $this->getView();
- return $class::widget($sorter);
- }
+ /**
+ * Renders the pager.
+ * @return string the rendering result
+ */
+ public function renderPager()
+ {
+ $pagination = $this->dataProvider->getPagination();
+ if ($pagination === false || $this->dataProvider->getCount() <= 0) {
+ return '';
+ }
+ /** @var LinkPager $class */
+ $class = ArrayHelper::remove($this->pager, 'class', LinkPager::className());
+ $pager = $this->pager;
+ $pager['pagination'] = $pagination;
+ $pager['view'] = $this->getView();
+
+ return $class::widget($pager);
+ }
+
+ /**
+ * Renders the sorter.
+ * @return string the rendering result
+ */
+ public function renderSorter()
+ {
+ $sort = $this->dataProvider->getSort();
+ if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) {
+ return '';
+ }
+ /** @var LinkSorter $class */
+ $class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className());
+ $sorter = $this->sorter;
+ $sorter['sort'] = $sort;
+ $sorter['view'] = $this->getView();
+
+ return $class::widget($sorter);
+ }
}
diff --git a/framework/widgets/Block.php b/framework/widgets/Block.php
index 4eec21794cc..c1813ff806d 100644
--- a/framework/widgets/Block.php
+++ b/framework/widgets/Block.php
@@ -15,31 +15,31 @@
*/
class Block extends Widget
{
- /**
- * @var boolean whether to render the block content in place. Defaults to false,
- * meaning the captured block content will not be displayed.
- */
- public $renderInPlace = false;
+ /**
+ * @var boolean whether to render the block content in place. Defaults to false,
+ * meaning the captured block content will not be displayed.
+ */
+ public $renderInPlace = false;
- /**
- * Starts recording a block.
- */
- public function init()
- {
- ob_start();
- ob_implicit_flush(false);
- }
+ /**
+ * Starts recording a block.
+ */
+ public function init()
+ {
+ ob_start();
+ ob_implicit_flush(false);
+ }
- /**
- * Ends recording a block.
- * This method stops output buffering and saves the rendering result as a named block in the view.
- */
- public function run()
- {
- $block = ob_get_clean();
- if ($this->renderInPlace) {
- echo $block;
- }
- $this->view->blocks[$this->getId()] = $block;
- }
+ /**
+ * Ends recording a block.
+ * This method stops output buffering and saves the rendering result as a named block in the view.
+ */
+ public function run()
+ {
+ $block = ob_get_clean();
+ if ($this->renderInPlace) {
+ echo $block;
+ }
+ $this->view->blocks[$this->getId()] = $block;
+ }
}
diff --git a/framework/widgets/Breadcrumbs.php b/framework/widgets/Breadcrumbs.php
index 195ef07523f..e4936373373 100644
--- a/framework/widgets/Breadcrumbs.php
+++ b/framework/widgets/Breadcrumbs.php
@@ -47,97 +47,97 @@
*/
class Breadcrumbs extends Widget
{
- /**
- * @var string the name of the breadcrumb container tag.
- */
- public $tag = 'ul';
- /**
- * @var array the HTML attributes for the breadcrumb container tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'breadcrumb'];
- /**
- * @var boolean whether to HTML-encode the link labels.
- */
- public $encodeLabels = true;
- /**
- * @var array the first hyperlink in the breadcrumbs (called home link).
- * Please refer to [[links]] on the format of the link.
- * If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]]
- * with the label 'Home'. If this property is false, the home link will not be rendered.
- */
- public $homeLink;
- /**
- * @var array list of links to appear in the breadcrumbs. If this property is empty,
- * the widget will not render anything. Each array element represents a single link in the breadcrumbs
- * with the following structure:
- *
- * ~~~
- * [
- * 'label' => 'label of the link', // required
- * 'url' => 'url of the link', // optional, will be processed by Url::to()
- * ]
- * ~~~
- *
- * If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`,
- * you should simply use `$label`.
- */
- public $links = [];
- /**
- * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}`
- * will be replaced with the actual HTML link for each inactive item.
- */
- public $itemTemplate = "
{link}
\n";
- /**
- * @var string the template used to render each active item in the breadcrumbs. The token `{link}`
- * will be replaced with the actual HTML link for each active item.
- */
- public $activeItemTemplate = "
{link}
\n";
+ /**
+ * @var string the name of the breadcrumb container tag.
+ */
+ public $tag = 'ul';
+ /**
+ * @var array the HTML attributes for the breadcrumb container tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'breadcrumb'];
+ /**
+ * @var boolean whether to HTML-encode the link labels.
+ */
+ public $encodeLabels = true;
+ /**
+ * @var array the first hyperlink in the breadcrumbs (called home link).
+ * Please refer to [[links]] on the format of the link.
+ * If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]]
+ * with the label 'Home'. If this property is false, the home link will not be rendered.
+ */
+ public $homeLink;
+ /**
+ * @var array list of links to appear in the breadcrumbs. If this property is empty,
+ * the widget will not render anything. Each array element represents a single link in the breadcrumbs
+ * with the following structure:
+ *
+ * ~~~
+ * [
+ * 'label' => 'label of the link', // required
+ * 'url' => 'url of the link', // optional, will be processed by Url::to()
+ * ]
+ * ~~~
+ *
+ * If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`,
+ * you should simply use `$label`.
+ */
+ public $links = [];
+ /**
+ * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}`
+ * will be replaced with the actual HTML link for each inactive item.
+ */
+ public $itemTemplate = "
{link}
\n";
+ /**
+ * @var string the template used to render each active item in the breadcrumbs. The token `{link}`
+ * will be replaced with the actual HTML link for each active item.
+ */
+ public $activeItemTemplate = "
{link}
\n";
- /**
- * Renders the widget.
- */
- public function run()
- {
- if (empty($this->links)) {
- return;
- }
- $links = [];
- if ($this->homeLink === null) {
- $links[] = $this->renderItem([
- 'label' => Yii::t('yii', 'Home'),
- 'url' => Yii::$app->homeUrl,
- ], $this->itemTemplate);
- } elseif ($this->homeLink !== false) {
- $links[] = $this->renderItem($this->homeLink, $this->itemTemplate);
- }
- foreach ($this->links as $link) {
- if (!is_array($link)) {
- $link = ['label' => $link];
- }
- $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
- }
- echo Html::tag($this->tag, implode('', $links), $this->options);
- }
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ if (empty($this->links)) {
+ return;
+ }
+ $links = [];
+ if ($this->homeLink === null) {
+ $links[] = $this->renderItem([
+ 'label' => Yii::t('yii', 'Home'),
+ 'url' => Yii::$app->homeUrl,
+ ], $this->itemTemplate);
+ } elseif ($this->homeLink !== false) {
+ $links[] = $this->renderItem($this->homeLink, $this->itemTemplate);
+ }
+ foreach ($this->links as $link) {
+ if (!is_array($link)) {
+ $link = ['label' => $link];
+ }
+ $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
+ }
+ echo Html::tag($this->tag, implode('', $links), $this->options);
+ }
- /**
- * Renders a single breadcrumb item.
- * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
- * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link.
- * @return string the rendering result
- * @throws InvalidConfigException if `$link` does not have "label" element.
- */
- protected function renderItem($link, $template)
- {
- if (isset($link['label'])) {
- $label = $this->encodeLabels ? Html::encode($link['label']) : $link['label'];
- } else {
- throw new InvalidConfigException('The "label" element is required for each link.');
- }
- if (isset($link['url'])) {
- return strtr($template, ['{link}' => Html::a($label, $link['url'])]);
- } else {
- return strtr($template, ['{link}' => $label]);
- }
- }
+ /**
+ * Renders a single breadcrumb item.
+ * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
+ * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link.
+ * @return string the rendering result
+ * @throws InvalidConfigException if `$link` does not have "label" element.
+ */
+ protected function renderItem($link, $template)
+ {
+ if (isset($link['label'])) {
+ $label = $this->encodeLabels ? Html::encode($link['label']) : $link['label'];
+ } else {
+ throw new InvalidConfigException('The "label" element is required for each link.');
+ }
+ if (isset($link['url'])) {
+ return strtr($template, ['{link}' => Html::a($label, $link['url'])]);
+ } else {
+ return strtr($template, ['{link}' => $label]);
+ }
+ }
}
diff --git a/framework/widgets/ContentDecorator.php b/framework/widgets/ContentDecorator.php
index 9224f3551a7..160cd84d77e 100644
--- a/framework/widgets/ContentDecorator.php
+++ b/framework/widgets/ContentDecorator.php
@@ -16,37 +16,37 @@
*/
class ContentDecorator extends Widget
{
- /**
- * @var string the view file that will be used to decorate the content enclosed by this widget.
- * This can be specified as either the view file path or path alias.
- */
- public $viewFile;
- /**
- * @var array the parameters (name => value) to be extracted and made available in the decorative view.
- */
- public $params = [];
+ /**
+ * @var string the view file that will be used to decorate the content enclosed by this widget.
+ * This can be specified as either the view file path or path alias.
+ */
+ public $viewFile;
+ /**
+ * @var array the parameters (name => value) to be extracted and made available in the decorative view.
+ */
+ public $params = [];
- /**
- * Starts recording a clip.
- */
- public function init()
- {
- if ($this->viewFile === null) {
- throw new InvalidConfigException('ContentDecorator::viewFile must be set.');
- }
- ob_start();
- ob_implicit_flush(false);
- }
+ /**
+ * Starts recording a clip.
+ */
+ public function init()
+ {
+ if ($this->viewFile === null) {
+ throw new InvalidConfigException('ContentDecorator::viewFile must be set.');
+ }
+ ob_start();
+ ob_implicit_flush(false);
+ }
- /**
- * Ends recording a clip.
- * This method stops output buffering and saves the rendering result as a named clip in the controller.
- */
- public function run()
- {
- $params = $this->params;
- $params['content'] = ob_get_clean();
- // render under the existing context
- echo $this->view->renderFile($this->viewFile, $params);
- }
+ /**
+ * Ends recording a clip.
+ * This method stops output buffering and saves the rendering result as a named clip in the controller.
+ */
+ public function run()
+ {
+ $params = $this->params;
+ $params['content'] = ob_get_clean();
+ // render under the existing context
+ echo $this->view->renderFile($this->viewFile, $params);
+ }
}
diff --git a/framework/widgets/DetailView.php b/framework/widgets/DetailView.php
index e97b5763390..00987c21f11 100644
--- a/framework/widgets/DetailView.php
+++ b/framework/widgets/DetailView.php
@@ -48,170 +48,169 @@
*/
class DetailView extends Widget
{
- /**
- * @var array|object the data model whose details are to be displayed. This can be either a [[Model]] instance
- * or an associative array.
- */
- public $model;
- /**
- * @var array a list of attributes to be displayed in the detail view. Each array element
- * represents the specification for displaying one particular attribute.
- *
- * An attribute can be specified as a string in the format of "attribute", "attribute:format" or "attribute:format:label",
- * where "attribute" refers to the attribute name, and "format" represents the format of the attribute. The "format"
- * is passed to the [[Formatter::format()]] method to format an attribute value into a displayable text.
- * Please refer to [[Formatter]] for the supported types. Both "format" and "label" are optional.
- * They will take default values if absent.
- *
- * An attribute can also be specified in terms of an array with the following elements:
- *
- * - attribute: the attribute name. This is required if either "label" or "value" is not specified.
- * - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
- * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
- * by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
- * according to the "format" option.
- * - format: the type of the value that determines how the value would be formatted into a displayable text.
- * Please refer to [[Formatter]] for supported types.
- * - visible: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed.
- */
- public $attributes;
- /**
- * @var string|callable the template used to render a single attribute. If a string, the token `{label}`
- * and `{value}` will be replaced with the label and the value of the corresponding attribute.
- * If a callback (e.g. an anonymous function), the signature must be as follows:
- *
- * ~~~
- * function ($attribute, $index, $widget)
- * ~~~
- *
- * where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based
- * index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance.
- */
- public $template = "
{label}
{value}
";
- /**
- * @var array the HTML attributes for the container tag of this widget. The "tag" option specifies
- * what container tag should be used. It defaults to "table" if not set.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'table table-striped table-bordered detail-view'];
- /**
- * @var array|Formatter the formatter used to format model attribute values into displayable texts.
- * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
- * instance. If this property is not set, the "formatter" application component will be used.
- */
- public $formatter;
+ /**
+ * @var array|object the data model whose details are to be displayed. This can be either a [[Model]] instance
+ * or an associative array.
+ */
+ public $model;
+ /**
+ * @var array a list of attributes to be displayed in the detail view. Each array element
+ * represents the specification for displaying one particular attribute.
+ *
+ * An attribute can be specified as a string in the format of "attribute", "attribute:format" or "attribute:format:label",
+ * where "attribute" refers to the attribute name, and "format" represents the format of the attribute. The "format"
+ * is passed to the [[Formatter::format()]] method to format an attribute value into a displayable text.
+ * Please refer to [[Formatter]] for the supported types. Both "format" and "label" are optional.
+ * They will take default values if absent.
+ *
+ * An attribute can also be specified in terms of an array with the following elements:
+ *
+ * - attribute: the attribute name. This is required if either "label" or "value" is not specified.
+ * - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
+ * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
+ * by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
+ * according to the "format" option.
+ * - format: the type of the value that determines how the value would be formatted into a displayable text.
+ * Please refer to [[Formatter]] for supported types.
+ * - visible: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed.
+ */
+ public $attributes;
+ /**
+ * @var string|callable the template used to render a single attribute. If a string, the token `{label}`
+ * and `{value}` will be replaced with the label and the value of the corresponding attribute.
+ * If a callback (e.g. an anonymous function), the signature must be as follows:
+ *
+ * ~~~
+ * function ($attribute, $index, $widget)
+ * ~~~
+ *
+ * where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based
+ * index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance.
+ */
+ public $template = "
{label}
{value}
";
+ /**
+ * @var array the HTML attributes for the container tag of this widget. The "tag" option specifies
+ * what container tag should be used. It defaults to "table" if not set.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'table table-striped table-bordered detail-view'];
+ /**
+ * @var array|Formatter the formatter used to format model attribute values into displayable texts.
+ * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
+ * instance. If this property is not set, the "formatter" application component will be used.
+ */
+ public $formatter;
+ /**
+ * Initializes the detail view.
+ * This method will initialize required property values.
+ */
+ public function init()
+ {
+ if ($this->model === null) {
+ throw new InvalidConfigException('Please specify the "model" property.');
+ }
+ if ($this->formatter == null) {
+ $this->formatter = Yii::$app->getFormatter();
+ } elseif (is_array($this->formatter)) {
+ $this->formatter = Yii::createObject($this->formatter);
+ }
+ if (!$this->formatter instanceof Formatter) {
+ throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
+ }
+ $this->normalizeAttributes();
+ }
- /**
- * Initializes the detail view.
- * This method will initialize required property values.
- */
- public function init()
- {
- if ($this->model === null) {
- throw new InvalidConfigException('Please specify the "model" property.');
- }
- if ($this->formatter == null) {
- $this->formatter = Yii::$app->getFormatter();
- } elseif (is_array($this->formatter)) {
- $this->formatter = Yii::createObject($this->formatter);
- }
- if (!$this->formatter instanceof Formatter) {
- throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
- }
- $this->normalizeAttributes();
- }
+ /**
+ * Renders the detail view.
+ * This is the main entry of the whole detail view rendering.
+ */
+ public function run()
+ {
+ $rows = [];
+ $i = 0;
+ foreach ($this->attributes as $attribute) {
+ $rows[] = $this->renderAttribute($attribute, $i++);
+ }
- /**
- * Renders the detail view.
- * This is the main entry of the whole detail view rendering.
- */
- public function run()
- {
- $rows = [];
- $i = 0;
- foreach ($this->attributes as $attribute) {
- $rows[] = $this->renderAttribute($attribute, $i++);
- }
+ $tag = ArrayHelper::remove($this->options, 'tag', 'table');
+ echo Html::tag($tag, implode("\n", $rows), $this->options);
+ }
- $tag = ArrayHelper::remove($this->options, 'tag', 'table');
- echo Html::tag($tag, implode("\n", $rows), $this->options);
- }
+ /**
+ * Renders a single attribute.
+ * @param array $attribute the specification of the attribute to be rendered.
+ * @param integer $index the zero-based index of the attribute in the [[attributes]] array
+ * @return string the rendering result
+ */
+ protected function renderAttribute($attribute, $index)
+ {
+ if (is_string($this->template)) {
+ return strtr($this->template, [
+ '{label}' => $attribute['label'],
+ '{value}' => $this->formatter->format($attribute['value'], $attribute['format']),
+ ]);
+ } else {
+ return call_user_func($this->template, $attribute, $index, $this);
+ }
+ }
- /**
- * Renders a single attribute.
- * @param array $attribute the specification of the attribute to be rendered.
- * @param integer $index the zero-based index of the attribute in the [[attributes]] array
- * @return string the rendering result
- */
- protected function renderAttribute($attribute, $index)
- {
- if (is_string($this->template)) {
- return strtr($this->template, [
- '{label}' => $attribute['label'],
- '{value}' => $this->formatter->format($attribute['value'], $attribute['format']),
- ]);
- } else {
- return call_user_func($this->template, $attribute, $index, $this);
- }
- }
+ /**
+ * Normalizes the attribute specifications.
+ * @throws InvalidConfigException
+ */
+ protected function normalizeAttributes()
+ {
+ if ($this->attributes === null) {
+ if ($this->model instanceof Model) {
+ $this->attributes = $this->model->attributes();
+ } elseif (is_object($this->model)) {
+ $this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model));
+ } elseif (is_array($this->model)) {
+ $this->attributes = array_keys($this->model);
+ } else {
+ throw new InvalidConfigException('The "model" property must be either an array or an object.');
+ }
+ sort($this->attributes);
+ }
- /**
- * Normalizes the attribute specifications.
- * @throws InvalidConfigException
- */
- protected function normalizeAttributes()
- {
- if ($this->attributes === null) {
- if ($this->model instanceof Model) {
- $this->attributes = $this->model->attributes();
- } elseif (is_object($this->model)) {
- $this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model));
- } elseif (is_array($this->model)) {
- $this->attributes = array_keys($this->model);
- } else {
- throw new InvalidConfigException('The "model" property must be either an array or an object.');
- }
- sort($this->attributes);
- }
+ foreach ($this->attributes as $i => $attribute) {
+ if (is_string($attribute)) {
+ if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $attribute, $matches)) {
+ throw new InvalidConfigException('The attribute must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"');
+ }
+ $attribute = [
+ 'attribute' => $matches[1],
+ 'format' => isset($matches[3]) ? $matches[3] : 'text',
+ 'label' => isset($matches[5]) ? $matches[5] : null,
+ ];
+ }
- foreach ($this->attributes as $i => $attribute) {
- if (is_string($attribute)) {
- if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $attribute, $matches)) {
- throw new InvalidConfigException('The attribute must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"');
- }
- $attribute = [
- 'attribute' => $matches[1],
- 'format' => isset($matches[3]) ? $matches[3] : 'text',
- 'label' => isset($matches[5]) ? $matches[5] : null,
- ];
- }
+ if (!is_array($attribute)) {
+ throw new InvalidConfigException('The attribute configuration must be an array.');
+ }
- if (!is_array($attribute)) {
- throw new InvalidConfigException('The attribute configuration must be an array.');
- }
+ if (isset($attribute['visible']) && !$attribute['visible']) {
+ unset($this->attributes[$i]);
+ continue;
+ }
- if (isset($attribute['visible']) && !$attribute['visible']) {
- unset($this->attributes[$i]);
- continue;
- }
+ if (!isset($attribute['format'])) {
+ $attribute['format'] = 'text';
+ }
+ if (isset($attribute['attribute'])) {
+ $attributeName = $attribute['attribute'];
+ if (!isset($attribute['label'])) {
+ $attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($attributeName) : Inflector::camel2words($attributeName, true);
+ }
+ if (!array_key_exists('value', $attribute)) {
+ $attribute['value'] = ArrayHelper::getValue($this->model, $attributeName);
+ }
+ } elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) {
+ throw new InvalidConfigException('The attribute configuration requires the "attribute" element to determine the value and display label.');
+ }
- if (!isset($attribute['format'])) {
- $attribute['format'] = 'text';
- }
- if (isset($attribute['attribute'])) {
- $attributeName = $attribute['attribute'];
- if (!isset($attribute['label'])) {
- $attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($attributeName) : Inflector::camel2words($attributeName, true);
- }
- if (!array_key_exists('value', $attribute)) {
- $attribute['value'] = ArrayHelper::getValue($this->model, $attributeName);
- }
- } elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) {
- throw new InvalidConfigException('The attribute configuration requires the "attribute" element to determine the value and display label.');
- }
-
- $this->attributes[$i] = $attribute;
- }
- }
+ $this->attributes[$i] = $attribute;
+ }
+ }
}
diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php
index 57c4659f33d..30f7a289017 100644
--- a/framework/widgets/FragmentCache.php
+++ b/framework/widgets/FragmentCache.php
@@ -22,157 +22,160 @@
*/
class FragmentCache extends Widget
{
- /**
- * @var Cache|string the cache object or the application component ID of the cache object.
- * After the FragmentCache object is created, if you want to change this property,
- * you should only assign it with a cache object.
- */
- public $cache = 'cache';
- /**
- * @var integer number of seconds that the data can remain valid in cache.
- * Use 0 to indicate that the cached data will never expire.
- */
- public $duration = 60;
- /**
- * @var array|Dependency the dependency that the cached content depends on.
- * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
- * For example,
- *
- * ~~~
- * [
- * 'class' => 'yii\caching\DbDependency',
- * 'sql' => 'SELECT MAX(lastModified) FROM Post',
- * ]
- * ~~~
- *
- * would make the output cache depends on the last modified time of all posts.
- * If any post has its modification time changed, the cached content would be invalidated.
- */
- public $dependency;
- /**
- * @var array list of factors that would cause the variation of the content being cached.
- * Each factor is a string representing a variation (e.g. the language, a GET parameter).
- * The following variation setting will cause the content to be cached in different versions
- * according to the current application language:
- *
- * ~~~
- * [
- * Yii::$app->language,
- * ]
- */
- public $variations;
- /**
- * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
- * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
- */
- public $enabled = true;
- /**
- * @var array a list of placeholders for embedding dynamic contents. This property
- * is used internally to implement the content caching feature. Do not modify it.
- */
- public $dynamicPlaceholders;
+ /**
+ * @var Cache|string the cache object or the application component ID of the cache object.
+ * After the FragmentCache object is created, if you want to change this property,
+ * you should only assign it with a cache object.
+ */
+ public $cache = 'cache';
+ /**
+ * @var integer number of seconds that the data can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ */
+ public $duration = 60;
+ /**
+ * @var array|Dependency the dependency that the cached content depends on.
+ * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
+ * For example,
+ *
+ * ~~~
+ * [
+ * 'class' => 'yii\caching\DbDependency',
+ * 'sql' => 'SELECT MAX(lastModified) FROM Post',
+ * ]
+ * ~~~
+ *
+ * would make the output cache depends on the last modified time of all posts.
+ * If any post has its modification time changed, the cached content would be invalidated.
+ */
+ public $dependency;
+ /**
+ * @var array list of factors that would cause the variation of the content being cached.
+ * Each factor is a string representing a variation (e.g. the language, a GET parameter).
+ * The following variation setting will cause the content to be cached in different versions
+ * according to the current application language:
+ *
+ * ~~~
+ * [
+ * Yii::$app->language,
+ * ]
+ */
+ public $variations;
+ /**
+ * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
+ * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
+ */
+ public $enabled = true;
+ /**
+ * @var array a list of placeholders for embedding dynamic contents. This property
+ * is used internally to implement the content caching feature. Do not modify it.
+ */
+ public $dynamicPlaceholders;
- /**
- * Initializes the FragmentCache object.
- */
- public function init()
- {
- parent::init();
+ /**
+ * Initializes the FragmentCache object.
+ */
+ public function init()
+ {
+ parent::init();
- if (!$this->enabled) {
- $this->cache = null;
- } elseif (is_string($this->cache)) {
- $this->cache = Yii::$app->getComponent($this->cache);
- }
+ if (!$this->enabled) {
+ $this->cache = null;
+ } elseif (is_string($this->cache)) {
+ $this->cache = Yii::$app->getComponent($this->cache);
+ }
- if ($this->getCachedContent() === false) {
- $this->getView()->cacheStack[] = $this;
- ob_start();
- ob_implicit_flush(false);
- }
- }
+ if ($this->getCachedContent() === false) {
+ $this->getView()->cacheStack[] = $this;
+ ob_start();
+ ob_implicit_flush(false);
+ }
+ }
- /**
- * Marks the end of content to be cached.
- * Content displayed before this method call and after [[init()]]
- * will be captured and saved in cache.
- * This method does nothing if valid content is already found in cache.
- */
- public function run()
- {
- if (($content = $this->getCachedContent()) !== false) {
- echo $content;
- } elseif ($this->cache instanceof Cache) {
- $content = ob_get_clean();
- array_pop($this->getView()->cacheStack);
- if (is_array($this->dependency)) {
- $this->dependency = Yii::createObject($this->dependency);
- }
- $data = [$content, $this->dynamicPlaceholders];
- $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
+ /**
+ * Marks the end of content to be cached.
+ * Content displayed before this method call and after [[init()]]
+ * will be captured and saved in cache.
+ * This method does nothing if valid content is already found in cache.
+ */
+ public function run()
+ {
+ if (($content = $this->getCachedContent()) !== false) {
+ echo $content;
+ } elseif ($this->cache instanceof Cache) {
+ $content = ob_get_clean();
+ array_pop($this->getView()->cacheStack);
+ if (is_array($this->dependency)) {
+ $this->dependency = Yii::createObject($this->dependency);
+ }
+ $data = [$content, $this->dynamicPlaceholders];
+ $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
- if (empty($this->getView()->cacheStack) && !empty($this->dynamicPlaceholders)) {
- $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
- }
- echo $content;
- }
- }
+ if (empty($this->getView()->cacheStack) && !empty($this->dynamicPlaceholders)) {
+ $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
+ }
+ echo $content;
+ }
+ }
- /**
- * @var string|boolean the cached content. False if the content is not cached.
- */
- private $_content;
+ /**
+ * @var string|boolean the cached content. False if the content is not cached.
+ */
+ private $_content;
- /**
- * Returns the cached content if available.
- * @return string|boolean the cached content. False is returned if valid content is not found in the cache.
- */
- public function getCachedContent()
- {
- if ($this->_content === null) {
- $this->_content = false;
- if ($this->cache instanceof Cache) {
- $key = $this->calculateKey();
- $data = $this->cache->get($key);
- if (is_array($data) && count($data) === 2) {
- list ($content, $placeholders) = $data;
- if (is_array($placeholders) && count($placeholders) > 0) {
- if (empty($this->getView()->cacheStack)) {
- // outermost cache: replace placeholder with dynamic content
- $content = $this->updateDynamicContent($content, $placeholders);
- }
- foreach ($placeholders as $name => $statements) {
- $this->getView()->addDynamicPlaceholder($name, $statements);
- }
- }
- $this->_content = $content;
- }
- }
- }
- return $this->_content;
- }
+ /**
+ * Returns the cached content if available.
+ * @return string|boolean the cached content. False is returned if valid content is not found in the cache.
+ */
+ public function getCachedContent()
+ {
+ if ($this->_content === null) {
+ $this->_content = false;
+ if ($this->cache instanceof Cache) {
+ $key = $this->calculateKey();
+ $data = $this->cache->get($key);
+ if (is_array($data) && count($data) === 2) {
+ list ($content, $placeholders) = $data;
+ if (is_array($placeholders) && count($placeholders) > 0) {
+ if (empty($this->getView()->cacheStack)) {
+ // outermost cache: replace placeholder with dynamic content
+ $content = $this->updateDynamicContent($content, $placeholders);
+ }
+ foreach ($placeholders as $name => $statements) {
+ $this->getView()->addDynamicPlaceholder($name, $statements);
+ }
+ }
+ $this->_content = $content;
+ }
+ }
+ }
- protected function updateDynamicContent($content, $placeholders)
- {
- foreach ($placeholders as $name => $statements) {
- $placeholders[$name] = $this->getView()->evaluateDynamicContent($statements);
- }
- return strtr($content, $placeholders);
- }
+ return $this->_content;
+ }
- /**
- * Generates a unique key used for storing the content in cache.
- * The key generated depends on both [[id]] and [[variations]].
- * @return mixed a valid cache key
- */
- protected function calculateKey()
- {
- $factors = [__CLASS__, $this->getId()];
- if (is_array($this->variations)) {
- foreach ($this->variations as $factor) {
- $factors[] = $factor;
- }
- }
- return $factors;
- }
+ protected function updateDynamicContent($content, $placeholders)
+ {
+ foreach ($placeholders as $name => $statements) {
+ $placeholders[$name] = $this->getView()->evaluateDynamicContent($statements);
+ }
+
+ return strtr($content, $placeholders);
+ }
+
+ /**
+ * Generates a unique key used for storing the content in cache.
+ * The key generated depends on both [[id]] and [[variations]].
+ * @return mixed a valid cache key
+ */
+ protected function calculateKey()
+ {
+ $factors = [__CLASS__, $this->getId()];
+ if (is_array($this->variations)) {
+ foreach ($this->variations as $factor) {
+ $factors[] = $factor;
+ }
+ }
+
+ return $factors;
+ }
}
diff --git a/framework/widgets/InputWidget.php b/framework/widgets/InputWidget.php
index 321ebb0aff5..197b8b00de5 100644
--- a/framework/widgets/InputWidget.php
+++ b/framework/widgets/InputWidget.php
@@ -25,49 +25,48 @@
*/
class InputWidget extends Widget
{
- /**
- * @var Model the data model that this widget is associated with.
- */
- public $model;
- /**
- * @var string the model attribute that this widget is associated with.
- */
- public $attribute;
- /**
- * @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
- */
- public $name;
- /**
- * @var string the input value.
- */
- public $value;
- /**
- * @var array the HTML attributes for the input tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
+ /**
+ * @var Model the data model that this widget is associated with.
+ */
+ public $model;
+ /**
+ * @var string the model attribute that this widget is associated with.
+ */
+ public $attribute;
+ /**
+ * @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
+ */
+ public $name;
+ /**
+ * @var string the input value.
+ */
+ public $value;
+ /**
+ * @var array the HTML attributes for the input tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ if (!$this->hasModel() && $this->name === null) {
+ throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
+ }
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
+ }
+ parent::init();
+ }
- /**
- * Initializes the widget.
- * If you override this method, make sure you call the parent implementation first.
- */
- public function init()
- {
- if (!$this->hasModel() && $this->name === null) {
- throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
- }
- if (!isset($this->options['id'])) {
- $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
- }
- parent::init();
- }
-
- /**
- * @return boolean whether this widget is associated with a data model.
- */
- protected function hasModel()
- {
- return $this->model instanceof Model && $this->attribute !== null;
- }
+ /**
+ * @return boolean whether this widget is associated with a data model.
+ */
+ protected function hasModel()
+ {
+ return $this->model instanceof Model && $this->attribute !== null;
+ }
}
diff --git a/framework/widgets/LinkPager.php b/framework/widgets/LinkPager.php
index 8c0a34ca149..0f49099f2a0 100644
--- a/framework/widgets/LinkPager.php
+++ b/framework/widgets/LinkPager.php
@@ -28,197 +28,199 @@
*/
class LinkPager extends Widget
{
- /**
- * @var Pagination the pagination object that this pager is associated with.
- * You must set this property in order to make LinkPager work.
- */
- public $pagination;
- /**
- * @var array HTML attributes for the pager container tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'pagination'];
- /**
- * @var array HTML attributes for the link in a pager container tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $linkOptions = [];
- /**
- * @var string the CSS class for the "first" page button.
- */
- public $firstPageCssClass = 'first';
- /**
- * @var string the CSS class for the "last" page button.
- */
- public $lastPageCssClass = 'last';
- /**
- * @var string the CSS class for the "previous" page button.
- */
- public $prevPageCssClass = 'prev';
- /**
- * @var string the CSS class for the "next" page button.
- */
- public $nextPageCssClass = 'next';
- /**
- * @var string the CSS class for the active (currently selected) page button.
- */
- public $activePageCssClass = 'active';
- /**
- * @var string the CSS class for the disabled page buttons.
- */
- public $disabledPageCssClass = 'disabled';
- /**
- * @var integer maximum number of page buttons that can be displayed. Defaults to 10.
- */
- public $maxButtonCount = 10;
- /**
- * @var string the label for the "next" page button. Note that this will NOT be HTML-encoded.
- * If this property is null, the "next" page button will not be displayed.
- */
- public $nextPageLabel = '»';
- /**
- * @var string the text label for the previous page button. Note that this will NOT be HTML-encoded.
- * If this property is null, the "previous" page button will not be displayed.
- */
- public $prevPageLabel = '«';
- /**
- * @var string the text label for the "first" page button. Note that this will NOT be HTML-encoded.
- * If this property is null, the "first" page button will not be displayed.
- */
- public $firstPageLabel;
- /**
- * @var string the text label for the "last" page button. Note that this will NOT be HTML-encoded.
- * If this property is null, the "last" page button will not be displayed.
- */
- public $lastPageLabel;
- /**
- * @var boolean whether to register link tags in the HTML header for prev, next, first and last page.
- * Defaults to `false` to avoid conflicts when multiple pagers are used on one page.
- * @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2
- * @see registerLinkTags()
- */
- public $registerLinkTags = false;
-
-
- /**
- * Initializes the pager.
- */
- public function init()
- {
- if ($this->pagination === null) {
- throw new InvalidConfigException('The "pagination" property must be set.');
- }
- }
-
- /**
- * Executes the widget.
- * This overrides the parent implementation by displaying the generated page buttons.
- */
- public function run()
- {
- if ($this->registerLinkTags) {
- $this->registerLinkTags();
- }
- echo $this->renderPageButtons();
- }
-
- /**
- * Registers relational link tags in the html header for prev, next, first and last page.
- * These links are generated using [[yii\data\Pagination::getLinks()]].
- * @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2
- */
- protected function registerLinkTags()
- {
- $view = $this->getView();
- foreach ($this->pagination->getLinks() as $rel => $href) {
- $view->registerLinkTag(['rel' => $rel, 'href' => $href], $rel);
- }
- }
-
- /**
- * Renders the page buttons.
- * @return string the rendering result
- */
- protected function renderPageButtons()
- {
- $buttons = [];
-
- $pageCount = $this->pagination->getPageCount();
- $currentPage = $this->pagination->getPage();
-
- // first page
- if ($this->firstPageLabel !== null) {
- $buttons[] = $this->renderPageButton($this->firstPageLabel, 0, $this->firstPageCssClass, $currentPage <= 0, false);
- }
-
- // prev page
- if ($this->prevPageLabel !== null) {
- if (($page = $currentPage - 1) < 0) {
- $page = 0;
- }
- $buttons[] = $this->renderPageButton($this->prevPageLabel, $page, $this->prevPageCssClass, $currentPage <= 0, false);
- }
-
- // internal pages
- list($beginPage, $endPage) = $this->getPageRange();
- for ($i = $beginPage; $i <= $endPage; ++$i) {
- $buttons[] = $this->renderPageButton($i + 1, $i, null, false, $i == $currentPage);
- }
-
- // next page
- if ($this->nextPageLabel !== null) {
- if (($page = $currentPage + 1) >= $pageCount - 1) {
- $page = $pageCount - 1;
- }
- $buttons[] = $this->renderPageButton($this->nextPageLabel, $page, $this->nextPageCssClass, $currentPage >= $pageCount - 1, false);
- }
-
- // last page
- if ($this->lastPageLabel !== null) {
- $buttons[] = $this->renderPageButton($this->lastPageLabel, $pageCount - 1, $this->lastPageCssClass, $currentPage >= $pageCount - 1, false);
- }
-
- return Html::tag('ul', implode("\n", $buttons), $this->options);
- }
-
- /**
- * Renders a page button.
- * You may override this method to customize the generation of page buttons.
- * @param string $label the text label for the button
- * @param integer $page the page number
- * @param string $class the CSS class for the page button.
- * @param boolean $disabled whether this page button is disabled
- * @param boolean $active whether this page button is active
- * @return string the rendering result
- */
- protected function renderPageButton($label, $page, $class, $disabled, $active)
- {
- $options = ['class' => $class === '' ? null : $class];
- if ($active) {
- Html::addCssClass($options, $this->activePageCssClass);
- }
- if ($disabled) {
- Html::addCssClass($options, $this->disabledPageCssClass);
- return Html::tag('li', Html::tag('span', $label), $options);
- }
- $linkOptions = $this->linkOptions;
- $linkOptions['data-page'] = $page;
- return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), $linkOptions), $options);
- }
-
- /**
- * @return array the begin and end pages that need to be displayed.
- */
- protected function getPageRange()
- {
- $currentPage = $this->pagination->getPage();
- $pageCount = $this->pagination->getPageCount();
-
- $beginPage = max(0, $currentPage - (int)($this->maxButtonCount / 2));
- if (($endPage = $beginPage + $this->maxButtonCount - 1) >= $pageCount) {
- $endPage = $pageCount - 1;
- $beginPage = max(0, $endPage - $this->maxButtonCount + 1);
- }
- return [$beginPage, $endPage];
- }
+ /**
+ * @var Pagination the pagination object that this pager is associated with.
+ * You must set this property in order to make LinkPager work.
+ */
+ public $pagination;
+ /**
+ * @var array HTML attributes for the pager container tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'pagination'];
+ /**
+ * @var array HTML attributes for the link in a pager container tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $linkOptions = [];
+ /**
+ * @var string the CSS class for the "first" page button.
+ */
+ public $firstPageCssClass = 'first';
+ /**
+ * @var string the CSS class for the "last" page button.
+ */
+ public $lastPageCssClass = 'last';
+ /**
+ * @var string the CSS class for the "previous" page button.
+ */
+ public $prevPageCssClass = 'prev';
+ /**
+ * @var string the CSS class for the "next" page button.
+ */
+ public $nextPageCssClass = 'next';
+ /**
+ * @var string the CSS class for the active (currently selected) page button.
+ */
+ public $activePageCssClass = 'active';
+ /**
+ * @var string the CSS class for the disabled page buttons.
+ */
+ public $disabledPageCssClass = 'disabled';
+ /**
+ * @var integer maximum number of page buttons that can be displayed. Defaults to 10.
+ */
+ public $maxButtonCount = 10;
+ /**
+ * @var string the label for the "next" page button. Note that this will NOT be HTML-encoded.
+ * If this property is null, the "next" page button will not be displayed.
+ */
+ public $nextPageLabel = '»';
+ /**
+ * @var string the text label for the previous page button. Note that this will NOT be HTML-encoded.
+ * If this property is null, the "previous" page button will not be displayed.
+ */
+ public $prevPageLabel = '«';
+ /**
+ * @var string the text label for the "first" page button. Note that this will NOT be HTML-encoded.
+ * If this property is null, the "first" page button will not be displayed.
+ */
+ public $firstPageLabel;
+ /**
+ * @var string the text label for the "last" page button. Note that this will NOT be HTML-encoded.
+ * If this property is null, the "last" page button will not be displayed.
+ */
+ public $lastPageLabel;
+ /**
+ * @var boolean whether to register link tags in the HTML header for prev, next, first and last page.
+ * Defaults to `false` to avoid conflicts when multiple pagers are used on one page.
+ * @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2
+ * @see registerLinkTags()
+ */
+ public $registerLinkTags = false;
+
+ /**
+ * Initializes the pager.
+ */
+ public function init()
+ {
+ if ($this->pagination === null) {
+ throw new InvalidConfigException('The "pagination" property must be set.');
+ }
+ }
+
+ /**
+ * Executes the widget.
+ * This overrides the parent implementation by displaying the generated page buttons.
+ */
+ public function run()
+ {
+ if ($this->registerLinkTags) {
+ $this->registerLinkTags();
+ }
+ echo $this->renderPageButtons();
+ }
+
+ /**
+ * Registers relational link tags in the html header for prev, next, first and last page.
+ * These links are generated using [[yii\data\Pagination::getLinks()]].
+ * @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2
+ */
+ protected function registerLinkTags()
+ {
+ $view = $this->getView();
+ foreach ($this->pagination->getLinks() as $rel => $href) {
+ $view->registerLinkTag(['rel' => $rel, 'href' => $href], $rel);
+ }
+ }
+
+ /**
+ * Renders the page buttons.
+ * @return string the rendering result
+ */
+ protected function renderPageButtons()
+ {
+ $buttons = [];
+
+ $pageCount = $this->pagination->getPageCount();
+ $currentPage = $this->pagination->getPage();
+
+ // first page
+ if ($this->firstPageLabel !== null) {
+ $buttons[] = $this->renderPageButton($this->firstPageLabel, 0, $this->firstPageCssClass, $currentPage <= 0, false);
+ }
+
+ // prev page
+ if ($this->prevPageLabel !== null) {
+ if (($page = $currentPage - 1) < 0) {
+ $page = 0;
+ }
+ $buttons[] = $this->renderPageButton($this->prevPageLabel, $page, $this->prevPageCssClass, $currentPage <= 0, false);
+ }
+
+ // internal pages
+ list($beginPage, $endPage) = $this->getPageRange();
+ for ($i = $beginPage; $i <= $endPage; ++$i) {
+ $buttons[] = $this->renderPageButton($i + 1, $i, null, false, $i == $currentPage);
+ }
+
+ // next page
+ if ($this->nextPageLabel !== null) {
+ if (($page = $currentPage + 1) >= $pageCount - 1) {
+ $page = $pageCount - 1;
+ }
+ $buttons[] = $this->renderPageButton($this->nextPageLabel, $page, $this->nextPageCssClass, $currentPage >= $pageCount - 1, false);
+ }
+
+ // last page
+ if ($this->lastPageLabel !== null) {
+ $buttons[] = $this->renderPageButton($this->lastPageLabel, $pageCount - 1, $this->lastPageCssClass, $currentPage >= $pageCount - 1, false);
+ }
+
+ return Html::tag('ul', implode("\n", $buttons), $this->options);
+ }
+
+ /**
+ * Renders a page button.
+ * You may override this method to customize the generation of page buttons.
+ * @param string $label the text label for the button
+ * @param integer $page the page number
+ * @param string $class the CSS class for the page button.
+ * @param boolean $disabled whether this page button is disabled
+ * @param boolean $active whether this page button is active
+ * @return string the rendering result
+ */
+ protected function renderPageButton($label, $page, $class, $disabled, $active)
+ {
+ $options = ['class' => $class === '' ? null : $class];
+ if ($active) {
+ Html::addCssClass($options, $this->activePageCssClass);
+ }
+ if ($disabled) {
+ Html::addCssClass($options, $this->disabledPageCssClass);
+
+ return Html::tag('li', Html::tag('span', $label), $options);
+ }
+ $linkOptions = $this->linkOptions;
+ $linkOptions['data-page'] = $page;
+
+ return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), $linkOptions), $options);
+ }
+
+ /**
+ * @return array the begin and end pages that need to be displayed.
+ */
+ protected function getPageRange()
+ {
+ $currentPage = $this->pagination->getPage();
+ $pageCount = $this->pagination->getPageCount();
+
+ $beginPage = max(0, $currentPage - (int) ($this->maxButtonCount / 2));
+ if (($endPage = $beginPage + $this->maxButtonCount - 1) >= $pageCount) {
+ $endPage = $pageCount - 1;
+ $beginPage = max(0, $endPage - $this->maxButtonCount + 1);
+ }
+
+ return [$beginPage, $endPage];
+ }
}
diff --git a/framework/widgets/LinkSorter.php b/framework/widgets/LinkSorter.php
index 3dc9e6f53ec..30706dc88a7 100644
--- a/framework/widgets/LinkSorter.php
+++ b/framework/widgets/LinkSorter.php
@@ -23,53 +23,53 @@
*/
class LinkSorter extends Widget
{
- /**
- * @var Sort the sort definition
- */
- public $sort;
- /**
- * @var array list of the attributes that support sorting. If not set, it will be determined
- * using [[Sort::attributes]].
- */
- public $attributes;
- /**
- * @var array HTML attributes for the sorter container tag.
- * @see \yii\helpers\Html::ul() for special attributes.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'sorter'];
+ /**
+ * @var Sort the sort definition
+ */
+ public $sort;
+ /**
+ * @var array list of the attributes that support sorting. If not set, it will be determined
+ * using [[Sort::attributes]].
+ */
+ public $attributes;
+ /**
+ * @var array HTML attributes for the sorter container tag.
+ * @see \yii\helpers\Html::ul() for special attributes.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'sorter'];
+ /**
+ * Initializes the sorter.
+ */
+ public function init()
+ {
+ if ($this->sort === null) {
+ throw new InvalidConfigException('The "sort" property must be set.');
+ }
+ }
- /**
- * Initializes the sorter.
- */
- public function init()
- {
- if ($this->sort === null) {
- throw new InvalidConfigException('The "sort" property must be set.');
- }
- }
+ /**
+ * Executes the widget.
+ * This method renders the sort links.
+ */
+ public function run()
+ {
+ echo $this->renderSortLinks();
+ }
- /**
- * Executes the widget.
- * This method renders the sort links.
- */
- public function run()
- {
- echo $this->renderSortLinks();
- }
+ /**
+ * Renders the sort links.
+ * @return string the rendering result
+ */
+ protected function renderSortLinks()
+ {
+ $attributes = empty($this->attributes) ? array_keys($this->sort->attributes) : $this->attributes;
+ $links = [];
+ foreach ($attributes as $name) {
+ $links[] = $this->sort->link($name);
+ }
- /**
- * Renders the sort links.
- * @return string the rendering result
- */
- protected function renderSortLinks()
- {
- $attributes = empty($this->attributes) ? array_keys($this->sort->attributes) : $this->attributes;
- $links = [];
- foreach ($attributes as $name) {
- $links[] = $this->sort->link($name);
- }
- return Html::ul($links, array_merge($this->options, ['encode' => false]));
- }
+ return Html::ul($links, array_merge($this->options, ['encode' => false]));
+ }
}
diff --git a/framework/widgets/ListView.php b/framework/widgets/ListView.php
index 66690bb2cfe..0825427edbd 100644
--- a/framework/widgets/ListView.php
+++ b/framework/widgets/ListView.php
@@ -18,92 +18,93 @@
*/
class ListView extends BaseListView
{
- /**
- * @var array the HTML attributes for the container of the rendering result of each data model.
- * The "tag" element specifies the tag name of the container element and defaults to "div".
- * If "tag" is false, it means no container element will be rendered.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $itemOptions = [];
- /**
- * @var string|callable the name of the view for rendering each data item, or a callback (e.g. an anonymous function)
- * for rendering each data item. If it specifies a view name, the following variables will
- * be available in the view:
- *
- * - `$model`: mixed, the data model
- * - `$key`: mixed, the key value associated with the data item
- * - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]].
- * - `$widget`: ListView, this widget instance
- *
- * Note that the view name is resolved into the view file by the current context of the [[view]] object.
- *
- * If this property is specified as a callback, it should have the following signature:
- *
- * ~~~
- * function ($model, $key, $index, $widget)
- * ~~~
- */
- public $itemView;
- /**
- * @var array additional parameters to be passed to [[itemView]] when it is being rendered.
- * This property is used only when [[itemView]] is a string representing a view name.
- */
- public $viewParams = [];
- /**
- * @var string the HTML code to be displayed between any two consecutive items.
- */
- public $separator = "\n";
- /**
- * @var array the HTML attributes for the container tag of the list view.
- * The "tag" element specifies the tag name of the container element and defaults to "div".
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'list-view'];
+ /**
+ * @var array the HTML attributes for the container of the rendering result of each data model.
+ * The "tag" element specifies the tag name of the container element and defaults to "div".
+ * If "tag" is false, it means no container element will be rendered.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $itemOptions = [];
+ /**
+ * @var string|callable the name of the view for rendering each data item, or a callback (e.g. an anonymous function)
+ * for rendering each data item. If it specifies a view name, the following variables will
+ * be available in the view:
+ *
+ * - `$model`: mixed, the data model
+ * - `$key`: mixed, the key value associated with the data item
+ * - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]].
+ * - `$widget`: ListView, this widget instance
+ *
+ * Note that the view name is resolved into the view file by the current context of the [[view]] object.
+ *
+ * If this property is specified as a callback, it should have the following signature:
+ *
+ * ~~~
+ * function ($model, $key, $index, $widget)
+ * ~~~
+ */
+ public $itemView;
+ /**
+ * @var array additional parameters to be passed to [[itemView]] when it is being rendered.
+ * This property is used only when [[itemView]] is a string representing a view name.
+ */
+ public $viewParams = [];
+ /**
+ * @var string the HTML code to be displayed between any two consecutive items.
+ */
+ public $separator = "\n";
+ /**
+ * @var array the HTML attributes for the container tag of the list view.
+ * The "tag" element specifies the tag name of the container element and defaults to "div".
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'list-view'];
+ /**
+ * Renders all data models.
+ * @return string the rendering result
+ */
+ public function renderItems()
+ {
+ $models = $this->dataProvider->getModels();
+ $keys = $this->dataProvider->getKeys();
+ $rows = [];
+ foreach (array_values($models) as $index => $model) {
+ $rows[] = $this->renderItem($model, $keys[$index], $index);
+ }
- /**
- * Renders all data models.
- * @return string the rendering result
- */
- public function renderItems()
- {
- $models = $this->dataProvider->getModels();
- $keys = $this->dataProvider->getKeys();
- $rows = [];
- foreach (array_values($models) as $index => $model) {
- $rows[] = $this->renderItem($model, $keys[$index], $index);
- }
- return implode($this->separator, $rows);
- }
+ return implode($this->separator, $rows);
+ }
- /**
- * Renders a single data model.
- * @param mixed $model the data model to be rendered
- * @param mixed $key the key value associated with the data model
- * @param integer $index the zero-based index of the data model in the model array returned by [[dataProvider]].
- * @return string the rendering result
- */
- public function renderItem($model, $key, $index)
- {
- if ($this->itemView === null) {
- $content = $key;
- } elseif (is_string($this->itemView)) {
- $content = $this->getView()->render($this->itemView, array_merge([
- 'model' => $model,
- 'key' => $key,
- 'index' => $index,
- 'widget' => $this,
- ], $this->viewParams));
- } else {
- $content = call_user_func($this->itemView, $model, $key, $index, $this);
- }
- $options = $this->itemOptions;
- $tag = ArrayHelper::remove($options, 'tag', 'div');
- if ($tag !== false) {
- $options['data-key'] = is_array($key) ? json_encode($key) : (string)$key;
- return Html::tag($tag, $content, $options);
- } else {
- return $content;
- }
- }
+ /**
+ * Renders a single data model.
+ * @param mixed $model the data model to be rendered
+ * @param mixed $key the key value associated with the data model
+ * @param integer $index the zero-based index of the data model in the model array returned by [[dataProvider]].
+ * @return string the rendering result
+ */
+ public function renderItem($model, $key, $index)
+ {
+ if ($this->itemView === null) {
+ $content = $key;
+ } elseif (is_string($this->itemView)) {
+ $content = $this->getView()->render($this->itemView, array_merge([
+ 'model' => $model,
+ 'key' => $key,
+ 'index' => $index,
+ 'widget' => $this,
+ ], $this->viewParams));
+ } else {
+ $content = call_user_func($this->itemView, $model, $key, $index, $this);
+ }
+ $options = $this->itemOptions;
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ if ($tag !== false) {
+ $options['data-key'] = is_array($key) ? json_encode($key) : (string) $key;
+
+ return Html::tag($tag, $content, $options);
+ } else {
+ return $content;
+ }
+ }
}
diff --git a/framework/widgets/MaskedInput.php b/framework/widgets/MaskedInput.php
index a060b1a6737..6ae22635cb0 100644
--- a/framework/widgets/MaskedInput.php
+++ b/framework/widgets/MaskedInput.php
@@ -36,99 +36,98 @@
*/
class MaskedInput extends InputWidget
{
- /**
- * @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined:
- *
- * - `a`: represents an alpha character (A-Z, a-z)
- * - `9`: represents a numeric character (0-9)
- * - `*`: represents an alphanumeric character (A-Z, a-z, 0-9)
- * - `?`: anything listed after '?' within the mask is considered optional user input
- *
- * Additional characters can be defined by specifying the [[charMap]] property.
- */
- public $mask;
- /**
- * @var array the mapping between mask characters and the corresponding patterns.
- * For example, `['~' => '[+-]']` specifies that the '~' character expects '+' or '-' input.
- * Defaults to null, meaning using the map as described in [[mask]].
- */
- public $charMap;
- /**
- * @var string the character prompting for user input. Defaults to underscore '_'.
- */
- public $placeholder;
- /**
- * @var string a JavaScript function callback that will be invoked when user finishes the input.
- */
- public $completed;
- /**
- * @var array the HTML attributes for the input tag.
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = ['class' => 'form-control'];
+ /**
+ * @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined:
+ *
+ * - `a`: represents an alpha character (A-Z, a-z)
+ * - `9`: represents a numeric character (0-9)
+ * - `*`: represents an alphanumeric character (A-Z, a-z, 0-9)
+ * - `?`: anything listed after '?' within the mask is considered optional user input
+ *
+ * Additional characters can be defined by specifying the [[charMap]] property.
+ */
+ public $mask;
+ /**
+ * @var array the mapping between mask characters and the corresponding patterns.
+ * For example, `['~' => '[+-]']` specifies that the '~' character expects '+' or '-' input.
+ * Defaults to null, meaning using the map as described in [[mask]].
+ */
+ public $charMap;
+ /**
+ * @var string the character prompting for user input. Defaults to underscore '_'.
+ */
+ public $placeholder;
+ /**
+ * @var string a JavaScript function callback that will be invoked when user finishes the input.
+ */
+ public $completed;
+ /**
+ * @var array the HTML attributes for the input tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = ['class' => 'form-control'];
+ /**
+ * Initializes the widget.
+ * @throws InvalidConfigException if the "mask" property is not set.
+ */
+ public function init()
+ {
+ parent::init();
+ if (empty($this->mask)) {
+ throw new InvalidConfigException('The "mask" property must be set.');
+ }
+ }
- /**
- * Initializes the widget.
- * @throws InvalidConfigException if the "mask" property is not set.
- */
- public function init()
- {
- parent::init();
- if (empty($this->mask)) {
- throw new InvalidConfigException('The "mask" property must be set.');
- }
- }
+ /**
+ * Runs the widget.
+ */
+ public function run()
+ {
+ if ($this->hasModel()) {
+ echo Html::activeTextInput($this->model, $this->attribute, $this->options);
+ } else {
+ echo Html::textInput($this->name, $this->value, $this->options);
+ }
+ $this->registerClientScript();
+ }
- /**
- * Runs the widget.
- */
- public function run()
- {
- if ($this->hasModel()) {
- echo Html::activeTextInput($this->model, $this->attribute, $this->options);
- } else {
- echo Html::textInput($this->name, $this->value, $this->options);
- }
- $this->registerClientScript();
- }
+ /**
+ * Registers the needed JavaScript.
+ */
+ public function registerClientScript()
+ {
+ $options = $this->getClientOptions();
+ $options = empty($options) ? '' : ',' . Json::encode($options);
+ $js = '';
+ if (is_array($this->charMap) && !empty($this->charMap)) {
+ $js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n";
+ }
+ $id = $this->options['id'];
+ $js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});";
+ $view = $this->getView();
+ MaskedInputAsset::register($view);
+ $view->registerJs($js);
+ }
- /**
- * Registers the needed JavaScript.
- */
- public function registerClientScript()
- {
- $options = $this->getClientOptions();
- $options = empty($options) ? '' : ',' . Json::encode($options);
- $js = '';
- if (is_array($this->charMap) && !empty($this->charMap)) {
- $js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n";
- }
- $id = $this->options['id'];
- $js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});";
- $view = $this->getView();
- MaskedInputAsset::register($view);
- $view->registerJs($js);
- }
+ /**
+ * @return array the options for the text field
+ */
+ protected function getClientOptions()
+ {
+ $options = [];
+ if ($this->placeholder !== null) {
+ $options['placeholder'] = $this->placeholder;
+ }
- /**
- * @return array the options for the text field
- */
- protected function getClientOptions()
- {
- $options = [];
- if ($this->placeholder !== null) {
- $options['placeholder'] = $this->placeholder;
- }
+ if ($this->completed !== null) {
+ if ($this->completed instanceof JsExpression) {
+ $options['completed'] = $this->completed;
+ } else {
+ $options['completed'] = new JsExpression($this->completed);
+ }
+ }
- if ($this->completed !== null) {
- if ($this->completed instanceof JsExpression) {
- $options['completed'] = $this->completed;
- } else {
- $options['completed'] = new JsExpression($this->completed);
- }
- }
-
- return $options;
- }
+ return $options;
+ }
}
diff --git a/framework/widgets/MaskedInputAsset.php b/framework/widgets/MaskedInputAsset.php
index 475cf34161c..90e85e75762 100644
--- a/framework/widgets/MaskedInputAsset.php
+++ b/framework/widgets/MaskedInputAsset.php
@@ -15,11 +15,11 @@
*/
class MaskedInputAsset extends AssetBundle
{
- public $sourcePath = '@yii/assets';
- public $js = [
- 'jquery.maskedinput.js',
- ];
- public $depends = [
- 'yii\web\YiiAsset',
- ];
+ public $sourcePath = '@yii/assets';
+ public $js = [
+ 'jquery.maskedinput.js',
+ ];
+ public $depends = [
+ 'yii\web\YiiAsset',
+ ];
}
diff --git a/framework/widgets/Menu.php b/framework/widgets/Menu.php
index eb0bc4ac33c..3678a1d30cf 100644
--- a/framework/widgets/Menu.php
+++ b/framework/widgets/Menu.php
@@ -48,265 +48,271 @@
*/
class Menu extends Widget
{
- /**
- * @var array list of menu items. Each menu item should be an array of the following structure:
- *
- * - label: string, optional, specifies the menu item label. When [[encodeLabels]] is true, the label
- * will be HTML-encoded. If the label is not specified, an empty string will be used.
- * - url: string or array, optional, specifies the URL of the menu item. It will be processed by [[Url::to]].
- * When this is set, the actual menu item content will be generated using [[linkTemplate]];
- * otherwise, [[labelTemplate]] will be used.
- * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
- * - items: array, optional, specifies the sub-menu items. Its format is the same as the parent items.
- * - active: boolean, optional, whether this menu item is in active state (currently selected).
- * If a menu item is active, its CSS class will be appended with [[activeCssClass]].
- * If this option is not set, the menu item will be set active automatically when the current request
- * is triggered by [[url]]. For more details, please refer to [[isItemActive()]].
- * - template: string, optional, the template used to render the content of this menu item.
- * The token `{url}` will be replaced by the URL associated with this menu item,
- * and the token `{label}` will be replaced by the label of the menu item.
- * If this option is not set, [[linkTemplate]] or [[labelTemplate]] will be used instead.
- * - options: array, optional, the HTML attributes for the menu container tag.
- */
- public $items = [];
- /**
- * @var array list of HTML attributes for the menu container tag. This will be overwritten
- * by the "options" set in individual [[items]]. The following special options are recognized:
- *
- * - tag: string, defaults to "li", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $itemOptions = [];
- /**
- * @var string the template used to render the body of a menu which is a link.
- * In this template, the token `{url}` will be replaced with the corresponding link URL;
- * while `{label}` will be replaced with the link text.
- * This property will be overridden by the `template` option set in individual menu items via [[items]].
- */
- public $linkTemplate = '{label}';
- /**
- * @var string the template used to render the body of a menu which is NOT a link.
- * In this template, the token `{label}` will be replaced with the label of the menu item.
- * This property will be overridden by the `template` option set in individual menu items via [[items]].
- */
- public $labelTemplate = '{label}';
- /**
- * @var string the template used to render a list of sub-menus.
- * In this template, the token `{items}` will be replaced with the renderer sub-menu items.
- */
- public $submenuTemplate = "\n
\n{items}\n
\n";
- /**
- * @var boolean whether the labels for menu items should be HTML-encoded.
- */
- public $encodeLabels = true;
- /**
- * @var string the CSS class to be appended to the active menu item.
- */
- public $activeCssClass = 'active';
- /**
- * @var boolean whether to automatically activate items according to whether their route setting
- * matches the currently requested route.
- * @see isItemActive()
- */
- public $activateItems = true;
- /**
- * @var boolean whether to activate parent menu items when one of the corresponding child menu items is active.
- * The activated parent menu items will also have its CSS classes appended with [[activeCssClass]].
- */
- public $activateParents = false;
- /**
- * @var boolean whether to hide empty menu items. An empty menu item is one whose `url` option is not
- * set and which has no visible child menu items.
- */
- public $hideEmptyItems = true;
- /**
- * @var array the HTML attributes for the menu's container tag. The following special options are recognized:
- *
- * - tag: string, defaults to "ul", the tag name of the item container tags.
- *
- * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
- */
- public $options = [];
- /**
- * @var string the CSS class that will be assigned to the first item in the main menu or each submenu.
- * Defaults to null, meaning no such CSS class will be assigned.
- */
- public $firstItemCssClass;
- /**
- * @var string the CSS class that will be assigned to the last item in the main menu or each submenu.
- * Defaults to null, meaning no such CSS class will be assigned.
- */
- public $lastItemCssClass;
- /**
- * @var string the route used to determine if a menu item is active or not.
- * If not set, it will use the route of the current request.
- * @see params
- * @see isItemActive()
- */
- public $route;
- /**
- * @var array the parameters used to determine if a menu item is active or not.
- * If not set, it will use `$_GET`.
- * @see route
- * @see isItemActive()
- */
- public $params;
+ /**
+ * @var array list of menu items. Each menu item should be an array of the following structure:
+ *
+ * - label: string, optional, specifies the menu item label. When [[encodeLabels]] is true, the label
+ * will be HTML-encoded. If the label is not specified, an empty string will be used.
+ * - url: string or array, optional, specifies the URL of the menu item. It will be processed by [[Url::to]].
+ * When this is set, the actual menu item content will be generated using [[linkTemplate]];
+ * otherwise, [[labelTemplate]] will be used.
+ * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
+ * - items: array, optional, specifies the sub-menu items. Its format is the same as the parent items.
+ * - active: boolean, optional, whether this menu item is in active state (currently selected).
+ * If a menu item is active, its CSS class will be appended with [[activeCssClass]].
+ * If this option is not set, the menu item will be set active automatically when the current request
+ * is triggered by [[url]]. For more details, please refer to [[isItemActive()]].
+ * - template: string, optional, the template used to render the content of this menu item.
+ * The token `{url}` will be replaced by the URL associated with this menu item,
+ * and the token `{label}` will be replaced by the label of the menu item.
+ * If this option is not set, [[linkTemplate]] or [[labelTemplate]] will be used instead.
+ * - options: array, optional, the HTML attributes for the menu container tag.
+ */
+ public $items = [];
+ /**
+ * @var array list of HTML attributes for the menu container tag. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "li", the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $itemOptions = [];
+ /**
+ * @var string the template used to render the body of a menu which is a link.
+ * In this template, the token `{url}` will be replaced with the corresponding link URL;
+ * while `{label}` will be replaced with the link text.
+ * This property will be overridden by the `template` option set in individual menu items via [[items]].
+ */
+ public $linkTemplate = '{label}';
+ /**
+ * @var string the template used to render the body of a menu which is NOT a link.
+ * In this template, the token `{label}` will be replaced with the label of the menu item.
+ * This property will be overridden by the `template` option set in individual menu items via [[items]].
+ */
+ public $labelTemplate = '{label}';
+ /**
+ * @var string the template used to render a list of sub-menus.
+ * In this template, the token `{items}` will be replaced with the renderer sub-menu items.
+ */
+ public $submenuTemplate = "\n