Commit 96e794b4 by Paul Klimov

Merge branch 'master' of github.com:yiisoft/yii2 into mongo

parents dfefd060 3db7a075
...@@ -70,7 +70,7 @@ at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix). ...@@ -70,7 +70,7 @@ at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix).
You can then install the application using the following command: You can then install the application using the following command:
~~~ ~~~
php composer.phar create-project --stability=alpha yiisoft/yii2-app-advanced advanced php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced advanced
~~~ ~~~
......
...@@ -52,7 +52,7 @@ at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix). ...@@ -52,7 +52,7 @@ at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix).
You can then install this application template using the following command: You can then install this application template using the following command:
~~~ ~~~
php composer.phar create-project --stability=alpha yiisoft/yii2-app-basic basic php composer.phar create-project --stability=dev yiisoft/yii2-app-basic basic
~~~ ~~~
Now you should be able to access the application through the following URL, assuming `basic` is the directory Now you should be able to access the application through the following URL, assuming `basic` is the directory
......
...@@ -48,6 +48,13 @@ $requirements = [ ...@@ -48,6 +48,13 @@ $requirements = [
'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>', 'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>',
'memo' => 'Required for MySQL database.', 'memo' => 'Required for MySQL database.',
], ],
[
'name' => 'PDO PostgreSQL extension',
'mandatory' => false,
'condition' => extension_loaded('pdo_pgsql'),
'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>',
'memo' => 'Required for PostgreSQL database.',
],
// Cache : // Cache :
[ [
'name' => 'Memcache extension', 'name' => 'Memcache extension',
......
...@@ -125,7 +125,7 @@ class BlogController extends Controller ...@@ -125,7 +125,7 @@ class BlogController extends Controller
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
if (\Yii::$app->request->isPost)) { if (\Yii::$app->request->isPost) {
$post->load($_POST); $post->load($_POST);
if ($post->save()) { if ($post->save()) {
$this->redirect(['view', 'id' => $post->id]); $this->redirect(['view', 'id' => $post->id]);
......
...@@ -2,8 +2,8 @@ URL Management ...@@ -2,8 +2,8 @@ URL Management
============== ==============
The concept of URL management in Yii fairly simple. URL management is based on the premise that the application uses internal routes and parameters The concept of URL management in Yii fairly simple. URL management is based on the premise that the application uses internal routes and parameters
everywhere. The framework itself will then translates routes into URLs, and translate URLs into routs, according to the URL manager's configuration. everywhere. The framework itself will then translates routes into URLs, and translate URLs into routes, according to the URL manager's configuration.
This approach allows you to change site-wide URLs merely by edited a single config file, without ever touching the application code. This approach allows you to change site-wide URLs merely by editing a single config file, without ever touching the application code.
Internal route Internal route
-------------- --------------
......
...@@ -61,7 +61,8 @@ class Module extends \yii\base\Module ...@@ -61,7 +61,8 @@ class Module extends \yii\base\Module
Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']); Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
}); });
foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) { $this->panels = array_merge($this->corePanels(), $this->panels);
foreach ($this->panels as $id => $config) {
$config['module'] = $this; $config['module'] = $this;
$config['id'] = $id; $config['id'] = $id;
$this->panels[$id] = Yii::createObject($config); $this->panels[$id] = Yii::createObject($config);
......
...@@ -31,6 +31,12 @@ class Panel extends Component ...@@ -31,6 +31,12 @@ class Panel extends Component
*/ */
public $module; public $module;
public $data; 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 * @return string name of the panel
......
...@@ -27,6 +27,15 @@ class DefaultController extends Controller ...@@ -27,6 +27,15 @@ class DefaultController extends Controller
*/ */
public $summary; public $summary;
public function actions()
{
$actions = [];
foreach($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
}
public function actionIndex() public function actionIndex()
{ {
return $this->render('index', ['manifest' => $this->getManifest()]); return $this->render('index', ['manifest' => $this->getManifest()]);
...@@ -82,7 +91,7 @@ class DefaultController extends Controller ...@@ -82,7 +91,7 @@ class DefaultController extends Controller
return $this->_manifest; return $this->_manifest;
} }
protected function loadData($tag) public function loadData($tag)
{ {
$manifest = $this->getManifest(); $manifest = $this->getManifest();
if (isset($manifest[$tag])) { if (isset($manifest[$tag])) {
......
...@@ -94,7 +94,7 @@ class Command extends Component ...@@ -94,7 +94,7 @@ class Command extends Component
*/ */
public function get($index, $type, $id, $options = []) public function get($index, $type, $id, $options = [])
{ {
return $this->db->get([$index, $type, $id], $options, null, [200, 404]); return $this->db->get([$index, $type, $id], $options, null);
} }
/** /**
......
...@@ -177,10 +177,10 @@ class Connection extends Component ...@@ -177,10 +177,10 @@ class Connection extends Component
return new QueryBuilder($this); return new QueryBuilder($this);
} }
public function get($url, $options = [], $body = null) public function get($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('GET', $this->createUrl($url, $options), $body); return $this->httpRequest('GET', $this->createUrl($url, $options), $body, $raw);
} }
public function head($url, $options = [], $body = null) public function head($url, $options = [], $body = null)
...@@ -189,37 +189,43 @@ class Connection extends Component ...@@ -189,37 +189,43 @@ class Connection extends Component
return $this->httpRequest('HEAD', $this->createUrl($url, $options), $body); return $this->httpRequest('HEAD', $this->createUrl($url, $options), $body);
} }
public function post($url, $options = [], $body = null) public function post($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('POST', $this->createUrl($url, $options), $body); return $this->httpRequest('POST', $this->createUrl($url, $options), $body, $raw);
} }
public function put($url, $options = [], $body = null) public function put($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('PUT', $this->createUrl($url, $options), $body); return $this->httpRequest('PUT', $this->createUrl($url, $options), $body, $raw);
} }
public function delete($url, $options = [], $body = null) public function delete($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body); return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body, $raw);
} }
private function createUrl($path, $options = []) private function createUrl($path, $options = [])
{ {
$url = implode('/', array_map(function($a) { if (!is_string($path)) {
return urlencode(is_array($a) ? implode(',', $a) : $a); $url = implode('/', array_map(function($a) {
}, $path)); return urlencode(is_array($a) ? implode(',', $a) : $a);
}, $path));
if (!empty($options)) { if (!empty($options)) {
$url .= '?' . http_build_query($options); $url .= '?' . http_build_query($options);
}
} else {
$url = $path;
if (!empty($options)) {
$url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($options);
}
} }
return [$this->nodes[$this->activeNode]['http_address'], $url]; return [$this->nodes[$this->activeNode]['http_address'], $url];
} }
protected function httpRequest($method, $url, $requestBody = null) protected function httpRequest($method, $url, $requestBody = null, $raw = false)
{ {
$method = strtoupper($method); $method = strtoupper($method);
...@@ -228,7 +234,7 @@ class Connection extends Component ...@@ -228,7 +234,7 @@ class Connection extends Component
$body = ''; $body = '';
$options = [ $options = [
CURLOPT_USERAGENT => 'Yii2 Framework ' . __CLASS__, CURLOPT_USERAGENT => 'Yii Framework 2 ' . __CLASS__,
CURLOPT_RETURNTRANSFER => false, CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false, CURLOPT_HEADER => false,
// http://www.php.net/manual/en/function.curl-setopt.php#82418 // http://www.php.net/manual/en/function.curl-setopt.php#82418
...@@ -264,8 +270,11 @@ class Connection extends Component ...@@ -264,8 +270,11 @@ class Connection extends Component
if (is_array($url)) { if (is_array($url)) {
list($host, $q) = $url; list($host, $q) = $url;
if (strncmp($host, 'inet[/', 6) == 0) { if (strncmp($host, 'inet[', 5) == 0) {
$host = substr($host, 6, -1); $host = substr($host, 5, -1);
if (($pos = strpos($host, '/')) !== false) {
$host = substr($host, $pos + 1);
}
} }
$profile = $method . ' ' . $q . '#' . $requestBody; $profile = $method . ' ' . $q . '#' . $requestBody;
$url = 'http://' . $host . '/' . $q; $url = 'http://' . $host . '/' . $q;
...@@ -312,7 +321,7 @@ class Connection extends Component ...@@ -312,7 +321,7 @@ class Connection extends Component
]); ]);
} }
if (isset($headers['content-type']) && !strncmp($headers['content-type'], 'application/json', 16)) { if (isset($headers['content-type']) && !strncmp($headers['content-type'], 'application/json', 16)) {
return Json::decode($body); return $raw ? $body : Json::decode($body);
} }
throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [ throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [
'requestMethod' => $method, 'requestMethod' => $method,
......
<?php
/**
* @author Carsten Brandt <mail@cebe.cc>
*/
namespace yii\elasticsearch;
use yii\base\Action;
use yii\base\NotSupportedException;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use Yii;
use yii\web\Response;
class DebugAction extends Action
{
/**
* @var string the connection id to use
*/
public $db;
/**
* @var Panel
*/
public $panel;
public function run($logId, $tag)
{
$this->controller->loadData($tag);
$timings = $this->panel->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
if (!isset($timings[$logId])) {
throw new HttpException(404, 'Log message not found.');
}
$message = $timings[$logId][1];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
} else {
$url = $message;
$body = null;
}
$method = mb_substr($url, 0, $pos = mb_strpos($url, ' '));
$url = mb_substr($url, $pos + 1);
$options = ['pretty' => true];
/** @var Connection $db */
$db = \Yii::$app->getComponent($this->db);
$time = microtime(true);
switch($method) {
case 'GET': $result = $db->get($url, $options, $body, true); break;
case 'POST': $result = $db->post($url, $options, $body, true); break;
case 'PUT': $result = $db->put($url, $options, $body, true); break;
case 'DELETE': $result = $db->delete($url, $options, $body, true); break;
case 'HEAD': $result = $db->head($url, $options, $body); break;
default:
throw new NotSupportedException("Request method '$method' is not supported by elasticsearch.");
}
$time = microtime(true) - $time;
if ($result === true) {
$result = '<span class="label label-success">success</span>';
} elseif ($result === false) {
$result = '<span class="label label-danger">no success</span>';
}
Yii::$app->response->format = Response::FORMAT_JSON;
return [
'time' => sprintf('%.1f ms', $time * 1000),
'result' => $result,
];
}
}
\ No newline at end of file
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace yii\elasticsearch; namespace yii\elasticsearch;
use yii\debug\Panel; use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\log\Logger; use yii\log\Logger;
use yii\helpers\Html; use yii\helpers\Html;
use yii\web\View; use yii\web\View;
...@@ -20,6 +21,17 @@ use yii\web\View; ...@@ -20,6 +21,17 @@ use yii\web\View;
*/ */
class DebugPanel extends Panel class DebugPanel extends Panel
{ {
public $db = 'elasticsearch';
public function init()
{
$this->actions['elasticsearch-query'] = [
'class' => 'yii\\elasticsearch\\DebugAction',
'panel' => $this,
'db' => $this->db,
];
}
public function getName() public function getName()
{ {
return 'Elasticsearch'; return 'Elasticsearch';
...@@ -47,13 +59,14 @@ EOD; ...@@ -47,13 +59,14 @@ EOD;
public function getDetail() public function getDetail()
{ {
$timings = $this->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
$rows = []; $rows = [];
$i = 0; $i = 0;
foreach ($this->data['messages'] as $log) { foreach ($timings as $logId => $timing) {
list ($message, $level, $category, $time, $traces) = $log; $duration = sprintf('%.1f ms', $timing[3] * 1000);
if ($level == Logger::LEVEL_PROFILE_BEGIN) { $message = $timing[1];
continue; $traces = $timing[4];
}
if (($pos = mb_strpos($message, "#")) !== false) { if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos); $url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1); $body = mb_substr($message, $pos + 1);
...@@ -66,48 +79,43 @@ EOD; ...@@ -66,48 +79,43 @@ EOD;
$traceString .= Html::ul($traces, [ $traceString .= Html::ul($traces, [
'class' => 'trace', 'class' => 'trace',
'item' => function ($trace) { 'item' => function ($trace) {
return "<li>{$trace['file']}({$trace['line']})</li>"; return "<li>{$trace['file']}({$trace['line']})</li>";
}, },
]); ]);
} }
$runLinks = ''; $ajaxUrl = Html::url(['elasticsearch-query', 'logId' => $logId, 'tag' => $this->tag]);
$c = 0; \Yii::$app->view->registerJs(<<<JS
\Yii::$app->elasticsearch->open(); $('#elastic-link-$i').on('click', function() {
foreach(\Yii::$app->elasticsearch->nodes as $node) { var result = $('#elastic-result-$i');
$pos = mb_strpos($url, ' '); result.html('Sending request...');
$type = mb_substr($url, 0, $pos); result.parent('tr').show();
if ($type == 'GET' && !empty($body)) {
$type = 'POST';
}
$host = $node['http_address'];
if (strncmp($host, 'inet[/', 6) == 0) {
$host = substr($host, 6, -1);
}
$nodeUrl = 'http://' . $host . '/' . mb_substr($url, $pos + 1);
$nodeUrl .= (strpos($nodeUrl, '?') === false) ? '?pretty=true' : '&pretty=true';
$nodeBody = json_encode($body);
\Yii::$app->view->registerJs(<<<JS
$('#elastic-link-$i-$c').on('click', function() {
$('#elastic-result-$i').html('Sending $type request to $nodeUrl...');
$('#elastic-result-$i').parent('tr').show();
$.ajax({ $.ajax({
type: "$type", type: "POST",
url: "$nodeUrl", url: "$ajaxUrl",
body: $nodeBody,
success: function( data ) { success: function( data ) {
$('#elastic-result-$i').html(data); $('#elastic-time-$i').html(data.time);
$('#elastic-result-$i').html(data.result);
}, },
dataType: "text" error: function(jqXHR, textStatus, errorThrown) {
$('#elastic-time-$i').html('');
$('#elastic-result-$i').html('<span style="color: #c00;">Error: ' + errorThrown + ' - ' + textStatus + '</span><br />' + jqXHR.responseText);
},
dataType: "json"
}); });
return false; return false;
}); });
JS JS
, View::POS_READY); , View::POS_READY);
$runLinks .= Html::a(isset($node['name']) ? $node['name'] : $node['http_address'], '#', ['id' => "elastic-link-$i-$c"]) . '<br/>'; $runLink = Html::a('run query', '#', ['id' => "elastic-link-$i"]) . '<br/>';
$c++; $rows[] = <<<HTML
} <tr>
$rows[] = "<tr><td style=\"width: 80%;\"><div><b>$url</b><br/><p>$body</p>$traceString</div></td><td style=\"width: 20%;\">$runLinks</td></tr><tr style=\"display: none;\"><td colspan=\"2\" id=\"elastic-result-$i\"></td></tr>"; <td style="width: 10%;">$duration</td>
<td style="width: 75%;"><div><b>$url</b><br/><p>$body</p>$traceString</div></td>
<td style="width: 15%;">$runLink</td>
</tr>
<tr style="display: none;"><td id="elastic-time-$i"></td><td colspan="3" id="elastic-result-$i"></td></tr>
HTML;
$i++; $i++;
} }
$rows = implode("\n", $rows); $rows = implode("\n", $rows);
...@@ -117,8 +125,9 @@ JS ...@@ -117,8 +125,9 @@ JS
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;"> <table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead> <thead>
<tr> <tr>
<th style="width: 80%;">Url / Query</th> <th style="width: 10%;">Time</th>
<th style="width: 20%;">Run Query on node</th> <th style="width: 75%;">Url / Query</th>
<th style="width: 15%;">Run Query on node</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -130,7 +139,7 @@ HTML; ...@@ -130,7 +139,7 @@ HTML;
private $_timings; private $_timings;
protected function calculateTimings() public function calculateTimings()
{ {
if ($this->_timings !== null) { if ($this->_timings !== null) {
return $this->_timings; return $this->_timings;
......
...@@ -172,3 +172,5 @@ Add the following to you application config to enable it: ...@@ -172,3 +172,5 @@ Add the following to you application config to enable it:
], ],
// ... // ...
``` ```
![elasticsearch DebugPanel](README-debug.png)
\ No newline at end of file
...@@ -348,7 +348,6 @@ class Generator extends \yii\gii\Generator ...@@ -348,7 +348,6 @@ class Generator extends \yii\gii\Generator
case Schema::TYPE_TIME: case Schema::TYPE_TIME:
case Schema::TYPE_DATETIME: case Schema::TYPE_DATETIME:
case Schema::TYPE_TIMESTAMP: case Schema::TYPE_TIMESTAMP:
case
$conditions[] = "\$this->addCondition(\$query, '{$column}');"; $conditions[] = "\$this->addCondition(\$query, '{$column}');";
break; break;
default: default:
......
...@@ -397,10 +397,10 @@ class Generator extends \yii\gii\Generator ...@@ -397,10 +397,10 @@ class Generator extends \yii\gii\Generator
} }
$name = $rawName = Inflector::id2camel($key, '_'); $name = $rawName = Inflector::id2camel($key, '_');
$i = 0; $i = 0;
while (isset($table->columns[$name])) { while (isset($table->columns[lcfirst($name)])) {
$name = $rawName . ($i++); $name = $rawName . ($i++);
} }
while (isset($relations[$className][$name])) { while (isset($relations[$className][lcfirst($name)])) {
$name = $rawName . ($i++); $name = $rawName . ($i++);
} }
......
...@@ -8,11 +8,8 @@ ...@@ -8,11 +8,8 @@
namespace yii\sphinx; namespace yii\sphinx;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\InvalidParamException; use yii\db\BaseActiveRecord;
use yii\base\Model;
use yii\base\ModelEvent;
use yii\base\NotSupportedException; use yii\base\NotSupportedException;
use yii\base\UnknownMethodException;
use yii\db\ActiveRelationInterface; use yii\db\ActiveRelationInterface;
use yii\db\StaleObjectException; use yii\db\StaleObjectException;
use yii\helpers\Inflector; use yii\helpers\Inflector;
...@@ -22,6 +19,9 @@ use Yii; ...@@ -22,6 +19,9 @@ use Yii;
/** /**
* ActiveRecord is the base class for classes representing relational data in terms of objects. * ActiveRecord is the base class for classes representing relational data in terms of objects.
* *
* Warning: optimistic lock will NOT work in case of updating fields (not attributes) for the
* runtime indexes!
*
* @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is * @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is
* read-only. * read-only.
* @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]]. * @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]].
...@@ -40,45 +40,9 @@ use Yii; ...@@ -40,45 +40,9 @@ use Yii;
* @author Paul Klimov <klimov.paul@gmail.com> * @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0 * @since 2.0
*/ */
abstract class ActiveRecord extends Model abstract class ActiveRecord extends BaseActiveRecord
{ {
/** /**
* @event Event an event that is triggered when the record is initialized via [[init()]].
*/
const EVENT_INIT = 'init';
/**
* @event Event an event that is triggered after the record is created and populated with query result.
*/
const EVENT_AFTER_FIND = 'afterFind';
/**
* @event ModelEvent an event that is triggered before inserting a record.
* You may set [[ModelEvent::isValid]] to be false to stop the insertion.
*/
const EVENT_BEFORE_INSERT = 'beforeInsert';
/**
* @event Event an event that is triggered after a record is inserted.
*/
const EVENT_AFTER_INSERT = 'afterInsert';
/**
* @event ModelEvent an event that is triggered before updating a record.
* You may set [[ModelEvent::isValid]] to be false to stop the update.
*/
const EVENT_BEFORE_UPDATE = 'beforeUpdate';
/**
* @event Event an event that is triggered after a record is updated.
*/
const EVENT_AFTER_UPDATE = 'afterUpdate';
/**
* @event ModelEvent an event that is triggered before deleting a record.
* You may set [[ModelEvent::isValid]] to be false to stop the deletion.
*/
const EVENT_BEFORE_DELETE = 'beforeDelete';
/**
* @event Event an event that is triggered after a record is deleted.
*/
const EVENT_AFTER_DELETE = 'afterDelete';
/**
* The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
*/ */
const OP_INSERT = 0x01; const OP_INSERT = 0x01;
...@@ -97,18 +61,6 @@ abstract class ActiveRecord extends Model ...@@ -97,18 +61,6 @@ abstract class ActiveRecord extends Model
const OP_ALL = 0x07; const OP_ALL = 0x07;
/** /**
* @var array attribute values indexed by attribute names
*/
private $_attributes = [];
/**
* @var array old attribute values indexed by attribute names.
*/
private $_oldAttributes;
/**
* @var array related models indexed by the relation names
*/
private $_related = [];
/**
* @var string current snippet value for this Active Record instance. * @var string current snippet value for this Active Record instance.
* It will be filled up automatically when instance found using [[Query::snippetCallback]] * It will be filled up automatically when instance found using [[Query::snippetCallback]]
* or [[ActiveQuery::snippetByModel()]]. * or [[ActiveQuery::snippetByModel()]].
...@@ -127,33 +79,6 @@ abstract class ActiveRecord extends Model ...@@ -127,33 +79,6 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Creates an [[ActiveQuery]] instance for query purpose.
*
* @param mixed $q the query parameter. This can be one of the followings:
*
* - a string: fulltext query by a query string and return the list
* of matching records.
* - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
* - null: return a new [[ActiveQuery]] object for further query purpose.
*
* @return ActiveQuery|ActiveRecord[]|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance
* is returned; when `$q` is a string, an array of ActiveRecord objects matching it will be returned;
* when `$q` is an array, an ActiveRecord object matching it will be returned (null
* will be returned if there is no matching).
* @see createQuery()
*/
public static function find($q = null)
{
$query = static::createQuery();
if (is_array($q)) {
return $query->where($q)->one();
} elseif ($q !== null) {
return $query->match($q)->all();
}
return $query;
}
/**
* Creates an [[ActiveQuery]] instance with a given SQL statement. * Creates an [[ActiveQuery]] instance with a given SQL statement.
* *
* Note that because the SQL statement is already specified, calling additional * Note that because the SQL statement is already specified, calling additional
...@@ -359,37 +284,6 @@ abstract class ActiveRecord extends Model ...@@ -359,37 +284,6 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Returns the name of the column that stores the lock version for implementing optimistic locking.
*
* Optimistic locking allows multiple users to access the same record for edits and avoids
* potential conflicts. In case when a user attempts to save the record upon some staled data
* (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
* and the update or deletion is skipped.
*
* Optimistic locking is only supported by [[update()]] and [[delete()]].
*
* To use optimistic locking:
*
* 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
* Override this method to return the name of this column.
* 2. In the Web form that collects the user input, add a hidden field that stores
* the lock version of the recording being updated.
* 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
* and implement necessary business logic (e.g. merging the changes, prompting stated data)
* to resolve the conflict.
*
* Warning: optimistic lock will NOT work in case of updating fields (not attributes) for the
* runtime indexes!
*
* @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;
}
/**
* Declares which operations should be performed within a transaction in different scenarios. * Declares which operations should be performed within a transaction in different scenarios.
* The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]], * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
* which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively. * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
...@@ -422,161 +316,6 @@ abstract class ActiveRecord extends Model ...@@ -422,161 +316,6 @@ abstract class ActiveRecord extends Model
} }
/** /**
* PHP getter magic method.
* This method is overridden so that attributes and related objects can be accessed like properties.
* @param string $name property name
* @return mixed property value
* @see getAttribute()
*/
public function __get($name)
{
if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
return $this->_attributes[$name];
} elseif ($this->hasAttribute($name)) {
return null;
} else {
if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
return $this->_related[$name];
}
$value = parent::__get($name);
if ($value instanceof ActiveRelationInterface) {
return $this->_related[$name] = $value->multiple ? $value->all() : $value->one();
} else {
return $value;
}
}
}
/**
* PHP setter magic method.
* This method is overridden so that AR attributes can be accessed like properties.
* @param string $name property name
* @param mixed $value property value
*/
public function __set($name, $value)
{
if ($this->hasAttribute($name)) {
$this->_attributes[$name] = $value;
} else {
parent::__set($name, $value);
}
}
/**
* Checks if a property value is null.
* This method overrides the parent implementation by checking if the named attribute is null or not.
* @param string $name the property name or the event name
* @return boolean whether the property value is null
*/
public function __isset($name)
{
try {
return $this->__get($name) !== null;
} catch (\Exception $e) {
return false;
}
}
/**
* Sets a component property to be null.
* This method overrides the parent implementation by clearing
* the specified attribute value.
* @param string $name the property name or the event name
*/
public function __unset($name)
{
if ($this->hasAttribute($name)) {
unset($this->_attributes[$name]);
} else {
if (isset($this->_related[$name])) {
unset($this->_related[$name]);
} else {
parent::__unset($name);
}
}
}
/**
* Declares a `has-one` relation.
* The declaration is returned in terms of an [[ActiveRelation]] instance
* through which the related record can be queried and retrieved back.
*
* A `has-one` relation means that there is at most one related record matching
* the criteria set by this relation, e.g., a particular index has one source.
*
* For example, to declare the `source` relation for `ArticleIndex` class, we can write
* the following code in the `ArticleIndex` class:
*
* ~~~
* public function getSource()
* {
* return $this->hasOne('db', ArticleContent::className(), ['article_id' => 'id']);
* }
* ~~~
*
* Note that in the above, the 'article_id' key in the `$link` parameter refers to an attribute name
* in the related class `ArticleContent`, while the 'id' value refers to an attribute name
* in the current AR class.
*
* Call methods declared in [[ActiveRelation]] to further customize the relation.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the attributes in the `$class` model, while the values of the array refer to the corresponding
* attributes in the index associated with this AR class.
* @return ActiveRelationInterface the relation object.
*/
public function hasOne($class, $link)
{
/** @var ActiveRecord $class */
return $class::createActiveRelation([
'modelClass' => $class,
'primaryModel' => $this,
'link' => $link,
'multiple' => false,
]);
}
/**
* Declares a `has-many` relation.
* The declaration is returned in terms of an [[ActiveRelationInterface]] 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., an article has many tags.
*
* For example, to declare the `tags` relation for `ArticleIndex` class, we can write
* the following code in the `ArticleIndex` class:
*
* ~~~
* public function getOrders()
* {
* return $this->hasMany('db', Tag::className(), ['id' => 'tag_id']);
* }
* ~~~
*
* Note that in the above, the 'id' key in the `$link` parameter refers to
* an attribute name in the related class `Tag`, while the 'tag_id' value refers to
* a multi value attribute name in the current AR class.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the columns in the table associated with the `$class` model, while the values of the
* array refer to the corresponding columns in the table associated with this AR class.
* @return ActiveRelationInterface the relation object.
*/
public function hasMany($class, $link)
{
/** @var ActiveRecord $class */
return $class::createActiveRelation([
'modelClass' => $class,
'primaryModel' => $this,
'link' => $link,
'multiple' => true,
]);
}
/**
* Creates an [[ActiveRelationInterface]] instance. * Creates an [[ActiveRelationInterface]] instance.
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance. * This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
* You may override this method to return a customized relation. * You may override this method to return a customized relation.
...@@ -589,36 +328,6 @@ abstract class ActiveRecord extends Model ...@@ -589,36 +328,6 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Populates the named relation with the related records.
* Note that this method does not check if the relation exists or not.
* @param string $name the relation name (case-sensitive)
* @param ActiveRecord|array|null the related records to be populated into the relation.
*/
public function populateRelation($name, $records)
{
$this->_related[$name] = $records;
}
/**
* Check whether the named relation has been populated with records.
* @param string $name the relation name (case-sensitive)
* @return bool whether relation has been populated with records.
*/
public function isRelationPopulated($name)
{
return array_key_exists($name, $this->_related);
}
/**
* Returns all populated relations.
* @return array an array of relation data indexed by relation names.
*/
public function getPopulatedRelations()
{
return $this->_related;
}
/**
* Returns the list of all attribute names of the model. * 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. * The default implementation will return all column names of the table associated with this AR class.
* @return array list of attribute names. * @return array list of attribute names.
...@@ -629,169 +338,6 @@ abstract class ActiveRecord extends Model ...@@ -629,169 +338,6 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Returns a value indicating whether the model has an attribute with the specified name.
* @param string $name the name of the attribute
* @return boolean whether the model has an attribute with the specified name.
*/
public function hasAttribute($name)
{
return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
}
/**
* Returns the named attribute value.
* If this record is the result of a query and the attribute is not loaded,
* null will be returned.
* @param string $name the attribute name
* @return mixed the attribute value. Null if the attribute is not set or does not exist.
* @see hasAttribute()
*/
public function getAttribute($name)
{
return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
}
/**
* Sets the named attribute value.
* @param string $name the attribute name
* @param mixed $value the attribute value.
* @throws InvalidParamException if the named attribute does not exist.
* @see hasAttribute()
*/
public function setAttribute($name, $value)
{
if ($this->hasAttribute($name)) {
$this->_attributes[$name] = $value;
} else {
throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
}
}
/**
* Returns the old attribute values.
* @return array the old attribute values (name-value pairs)
*/
public function getOldAttributes()
{
return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
}
/**
* Sets the old attribute values.
* All existing old attribute values will be discarded.
* @param array $values old attribute values to be set.
*/
public function setOldAttributes($values)
{
$this->_oldAttributes = $values;
}
/**
* Returns the old value of the named attribute.
* If this record is the result of a query and the attribute is not loaded,
* null will be returned.
* @param string $name the attribute name
* @return mixed the old attribute value. Null if the attribute is not loaded before
* or does not exist.
* @see hasAttribute()
*/
public function getOldAttribute($name)
{
return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
}
/**
* Sets the old value of the named attribute.
* @param string $name the attribute name
* @param mixed $value the old attribute value.
* @throws InvalidParamException if the named attribute does not exist.
* @see hasAttribute()
*/
public function setOldAttribute($name, $value)
{
if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
$this->_oldAttributes[$name] = $value;
} else {
throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
}
}
/**
* Returns a value indicating whether the named attribute has been changed.
* @param string $name the name of the attribute
* @return boolean whether the attribute has been changed
*/
public function isAttributeChanged($name)
{
if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
return $this->_attributes[$name] !== $this->_oldAttributes[$name];
} else {
return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
}
}
/**
* Returns the attribute values that have been modified since they are loaded or saved most recently.
* @param string[]|null $names the names of the attributes whose values may be returned if they are
* changed recently. If null, [[attributes()]] will be used.
* @return array the changed attribute values (name-value pairs)
*/
public function getDirtyAttributes($names = null)
{
if ($names === null) {
$names = $this->attributes();
}
$names = array_flip($names);
$attributes = [];
if ($this->_oldAttributes === null) {
foreach ($this->_attributes as $name => $value) {
if (isset($names[$name])) {
$attributes[$name] = $value;
}
}
} else {
foreach ($this->_attributes as $name => $value) {
if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
$attributes[$name] = $value;
}
}
}
return $attributes;
}
/**
* Saves the current record.
*
* This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
* when [[isNewRecord]] is false.
*
* For example, to save an article record:
*
* ~~~
* $customer = new Article; // or $customer = Article::find(['id' => $id]);
* $customer->id = $id;
* $customer->genre_id = $genreId;
* $customer->content = $email;
* $customer->save();
* ~~~
*
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be saved.
* @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 saving succeeds
*/
public function save($runValidation = true, $attributes = null)
{
if ($this->getIsNewRecord()) {
return $this->insert($runValidation, $attributes);
} else {
return $this->update($runValidation, $attributes) !== false;
}
}
/**
* Inserts a row into the associated Sphinx index using the attribute values of this record. * Inserts a row into the associated Sphinx index using the attribute values of this record.
* *
* This method performs the following steps in order: * This method performs the following steps in order:
...@@ -862,8 +408,8 @@ abstract class ActiveRecord extends Model ...@@ -862,8 +408,8 @@ abstract class ActiveRecord extends Model
} }
$values = $this->getDirtyAttributes($attributes); $values = $this->getDirtyAttributes($attributes);
if (empty($values)) { if (empty($values)) {
foreach ($this->primaryKey() as $key) { foreach ($this->getPrimaryKey(true) as $key => $value) {
$values[$key] = isset($this->_attributes[$key]) ? $this->_attributes[$key] : null; $values[$key] = $value;
} }
} }
$db = static::getDb(); $db = static::getDb();
...@@ -872,7 +418,7 @@ abstract class ActiveRecord extends Model ...@@ -872,7 +418,7 @@ abstract class ActiveRecord extends Model
return false; return false;
} }
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $value; $this->setOldAttribute($name, $value);
} }
$this->afterSave(true); $this->afterSave(true);
return true; return true;
...@@ -957,7 +503,7 @@ abstract class ActiveRecord extends Model ...@@ -957,7 +503,7 @@ abstract class ActiveRecord extends Model
* @see CActiveRecord::update() * @see CActiveRecord::update()
* @throws StaleObjectException * @throws StaleObjectException
*/ */
private function updateInternal($attributes = null) protected function updateInternal($attributes = null)
{ {
if (!$this->beforeSave(false)) { if (!$this->beforeSave(false)) {
return false; return false;
...@@ -1007,7 +553,7 @@ abstract class ActiveRecord extends Model ...@@ -1007,7 +553,7 @@ abstract class ActiveRecord extends Model
} }
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name]; $this->setOldAttribute($name, $this->getAttribute($name));
} }
$this->afterSave(false); $this->afterSave(false);
return $rows; return $rows;
...@@ -1050,7 +596,7 @@ abstract class ActiveRecord extends Model ...@@ -1050,7 +596,7 @@ abstract class ActiveRecord extends Model
if ($lock !== null && !$result) { if ($lock !== null && !$result) {
throw new StaleObjectException('The object being deleted is outdated.'); throw new StaleObjectException('The object being deleted is outdated.');
} }
$this->_oldAttributes = null; $this->setOldAttributes(null);
$this->afterDelete(); $this->afterDelete();
} }
if ($transaction !== null) { if ($transaction !== null) {
...@@ -1070,149 +616,6 @@ abstract class ActiveRecord extends Model ...@@ -1070,149 +616,6 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Returns a value indicating whether the current record is new.
* @return boolean whether the record is new and should be inserted when calling [[save()]].
*/
public function getIsNewRecord()
{
return $this->_oldAttributes === null;
}
/**
* Sets the value indicating whether the record is new.
* @param boolean $value whether the record is new and should be inserted when calling [[save()]].
* @see getIsNewRecord()
*/
public function setIsNewRecord($value)
{
$this->_oldAttributes = $value ? null : $this->_attributes;
}
/**
* Initializes the object.
* This method is called at the end of the constructor.
* The default implementation will trigger an [[EVENT_INIT]] event.
* If you override this method, make sure you call the parent implementation at the end
* to ensure triggering of the event.
*/
public function init()
{
parent::init();
$this->trigger(self::EVENT_INIT);
}
/**
* This method is called when the AR object is created and populated with the query result.
* The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
* When overriding this method, make sure you call the parent implementation to ensure the
* event is triggered.
*/
public function afterFind()
{
$this->trigger(self::EVENT_AFTER_FIND);
}
/**
* This method is called at the beginning of inserting or updating a record.
* The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
* or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
* When overriding this method, make sure you call the parent implementation like the following:
*
* ~~~
* public function beforeSave($insert)
* {
* if (parent::beforeSave($insert)) {
* // ...custom code here...
* return true;
* } else {
* return false;
* }
* }
* ~~~
*
* @param boolean $insert whether this method called while inserting a record.
* If false, it means the method is called while updating a record.
* @return boolean whether the insertion or updating should continue.
* If false, the insertion or updating will be cancelled.
*/
public function beforeSave($insert)
{
$event = new ModelEvent;
$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
return $event->isValid;
}
/**
* This method is called at the end of inserting or updating a record.
* The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
* or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false.
* When overriding this method, make sure you call the parent implementation so that
* the event is triggered.
* @param boolean $insert whether this method called while inserting a record.
* If false, it means the method is called while updating a record.
*/
public function afterSave($insert)
{
$this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE);
}
/**
* This method is invoked before deleting a record.
* The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
* When overriding this method, make sure you call the parent implementation like the following:
*
* ~~~
* public function beforeDelete()
* {
* if (parent::beforeDelete()) {
* // ...custom code here...
* return true;
* } else {
* return false;
* }
* }
* ~~~
*
* @return boolean whether the record should be deleted. Defaults to true.
*/
public function beforeDelete()
{
$event = new ModelEvent;
$this->trigger(self::EVENT_BEFORE_DELETE, $event);
return $event->isValid;
}
/**
* This method is invoked after deleting a record.
* The default implementation raises the [[EVENT_AFTER_DELETE]] event.
* You may override this method to do postprocessing after the record is deleted.
* Make sure you call the parent implementation so that the event is raised properly.
*/
public function afterDelete()
{
$this->trigger(self::EVENT_AFTER_DELETE);
}
/**
* Repopulates this active record with the latest data.
* @return boolean whether the row still exists in the database. If true, the latest data
* will be populated to this active record. Otherwise, this record will remain unchanged.
*/
public function refresh()
{
$record = $this->find($this->getPrimaryKey(true));
if ($record === null) {
return false;
}
foreach ($this->attributes() as $name) {
$this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
}
$this->_oldAttributes = $this->_attributes;
$this->_related = [];
return true;
}
/**
* Returns a value indicating whether the given active record is the same as the current one. * 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. * 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. * If one of the records [[isNewRecord|is new]] they are also considered not equal.
...@@ -1228,61 +631,6 @@ abstract class ActiveRecord extends Model ...@@ -1228,61 +631,6 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Returns the primary key value(s).
* @param boolean $asArray whether to return the primary key value as an array. If true,
* the return value will be an array with column names as keys and column values as values.
* Note that for composite primary keys, an array will always be returned regardless of this parameter value.
* @property mixed The primary key value. An array (column name => column value) is returned if
* the primary key is composite. A string is returned otherwise (null will be returned if
* the key value is null).
* @return mixed the primary key value. An array (column name => column value) is returned if the primary key
* is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
* the key value is null).
*/
public function getPrimaryKey($asArray = false)
{
$keys = $this->primaryKey();
if (count($keys) === 1 && !$asArray) {
return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
} else {
$values = [];
foreach ($keys as $name) {
$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
}
return $values;
}
}
/**
* Returns the old primary key value(s).
* This refers to the primary key value that is populated into the record
* after executing a find method (e.g. find(), findAll()).
* The value remains unchanged even if the primary key attribute is manually assigned with a different value.
* @param boolean $asArray whether to return the primary key value as an array. If true,
* the return value will be an array with column name as key and column value as value.
* If this is false (default), a scalar value will be returned for non-composite primary key.
* @property mixed The old primary key value. An array (column name => column value) is
* returned if the primary key is composite. A string is returned otherwise (null will be
* returned if the key value is null).
* @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
* is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
* the key value is null).
*/
public function getOldPrimaryKey($asArray = false)
{
$keys = $this->primaryKey();
if (count($keys) === 1 && !$asArray) {
return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
} else {
$values = [];
foreach ($keys as $name) {
$values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
}
return $values;
}
}
/**
* Creates an active record object using a row of data. * Creates an active record object using a row of data.
* This method is called by [[ActiveQuery]] to populate the query results * This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records. * into Active Records. It is not meant to be used to create new records.
...@@ -1299,64 +647,17 @@ abstract class ActiveRecord extends Model ...@@ -1299,64 +647,17 @@ abstract class ActiveRecord extends Model
if ($column->isMva) { if ($column->isMva) {
$value = explode(',', $value); $value = explode(',', $value);
} }
$record->_attributes[$name] = $value; $record->setAttribute($name, $value);
} else { } else {
$record->$name = $value; $record->$name = $value;
} }
} }
$record->_oldAttributes = $record->_attributes; $record->setOldAttributes($record->getAttributes());
$record->afterFind(); $record->afterFind();
return $record; return $record;
} }
/** /**
* Creates an active record instance.
* This method is called by [[create()]].
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* @param array $row row data to be populated into the record.
* @return ActiveRecord the newly created active record
*/
public static function instantiate($row)
{
return new static;
}
/**
* Returns whether there is an element at the specified offset.
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean whether there is an element at the specified offset.
*/
public function offsetExists($offset)
{
return $this->__isset($offset);
}
/**
* Returns the relation object with the specified name.
* A relation is defined by a getter method which returns an [[ActiveRelationInterface]] object.
* It can be declared in either the Active Record class itself or one of its behaviors.
* @param string $name the relation name
* @return ActiveRelationInterface the relation object
* @throws InvalidParamException if the named relation does not exist.
*/
public function getRelation($name)
{
$getter = 'get' . $name;
try {
$relation = $this->$getter();
if ($relation instanceof ActiveRelationInterface) {
return $relation;
} else {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
}
} catch (UnknownMethodException $e) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
}
}
/**
* Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. * 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]]. * @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]]. * @return boolean whether the specified operation is transactional in the current [[scenario]].
...@@ -1367,29 +668,4 @@ abstract class ActiveRecord extends Model ...@@ -1367,29 +668,4 @@ abstract class ActiveRecord extends Model
$transactions = $this->transactions(); $transactions = $this->transactions();
return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation); return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
} }
/**
* 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
* @throws \Exception on failure
*/
public function offsetSet($offset, $item)
{
// Bypass relation owner restriction to 'yii\db\ActiveRecord' at [[yii\db\ActiveRelationTrait::findWith()]]:
try {
$relation = $this->getRelation($offset);
if (is_object($relation)) {
$this->populateRelation($offset, $item);
return;
}
} catch (InvalidParamException $e) {
// shut down exception : has getter, but not relation
} catch (UnknownMethodException $e) {
throw $e->getPrevious();
}
parent::offsetSet($offset, $item);
}
} }
\ No newline at end of file
...@@ -128,7 +128,6 @@ ...@@ -128,7 +128,6 @@
data = $form.data('yiiActiveForm'); data = $form.data('yiiActiveForm');
if (data.validated) { if (data.validated) {
// continue submitting the form since validation passes // continue submitting the form since validation passes
data.validated = false;
return true; return true;
} }
......
...@@ -23,10 +23,13 @@ use yii\base\Component; ...@@ -23,10 +23,13 @@ use yii\base\Component;
* *
* ~~~ * ~~~
* $query = new Query; * $query = new Query;
* // compose the query
* $query->select('id, name') * $query->select('id, name')
* ->from('tbl_user') * ->from('tbl_user')
* ->limit(10); * ->limit(10);
* // build and execute the query * // build and execute the query
* $rows = $query->all();
* // alternatively, you can create DB command and execute it
* $command = $query->createCommand(); * $command = $query->createCommand();
* // $command->sql returns the actual SQL * // $command->sql returns the actual SQL
* $rows = $command->queryAll(); * $rows = $command->queryAll();
......
...@@ -327,6 +327,29 @@ class BaseArrayHelper ...@@ -327,6 +327,29 @@ class BaseArrayHelper
} }
/** /**
* 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. * 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 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 * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
......
...@@ -551,8 +551,11 @@ class BaseHtml ...@@ -551,8 +551,11 @@ class BaseHtml
* in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks.
* When this option is specified, the radio button will be enclosed by a label tag. * 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. * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
* - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified.
* If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered
* around the the radio button.
* *
* The rest of the options will be rendered as the attributes of the resulting tag. The values will * The rest of the options will be rendered as the attributes of the resulting radio button tag. The values will
* be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
* *
* @return string the generated radio button tag * @return string the generated radio button tag
...@@ -571,9 +574,14 @@ class BaseHtml ...@@ -571,9 +574,14 @@ class BaseHtml
if (isset($options['label'])) { if (isset($options['label'])) {
$label = $options['label']; $label = $options['label'];
$labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : [];
unset($options['label'], $options['labelOptions']); $container = isset($options['container']) ? $options['container'] : ['class' => 'radio'];
unset($options['label'], $options['labelOptions'], $options['container']);
$content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions);
return $hidden . static::tag('div', $content, ['class' => 'radio']); if (is_array($container)) {
return $hidden . static::tag('div', $content, $container);
} else {
return $hidden . $content;
}
} else { } else {
return $hidden . static::input('radio', $name, $value, $options); return $hidden . static::input('radio', $name, $value, $options);
} }
...@@ -592,8 +600,11 @@ class BaseHtml ...@@ -592,8 +600,11 @@ class BaseHtml
* in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks.
* When this option is specified, the checkbox will be enclosed by a label tag. * 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. * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
* - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified.
* If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered
* around the the radio button.
* *
* The rest of the options will be rendered as the attributes of the resulting tag. The values will * The rest of the options will be rendered as the attributes of the resulting checkbox tag. The values will
* be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
* *
* @return string the generated checkbox tag * @return string the generated checkbox tag
...@@ -612,9 +623,14 @@ class BaseHtml ...@@ -612,9 +623,14 @@ class BaseHtml
if (isset($options['label'])) { if (isset($options['label'])) {
$label = $options['label']; $label = $options['label'];
$labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : [];
unset($options['label'], $options['labelOptions']); $container = isset($options['container']) ? $options['container'] : ['class' => 'checkbox'];
unset($options['label'], $options['labelOptions'], $options['container']);
$content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions);
return $hidden . static::tag('div', $content, ['class' => 'checkbox']); if (is_array($container)) {
return $hidden . static::tag('div', $content, $container);
} else {
return $hidden . $content;
}
} else { } else {
return $hidden . static::input('checkbox', $name, $value, $options); return $hidden . static::input('checkbox', $name, $value, $options);
} }
......
<?php <?php
namespace yiiunit\data\sphinx\ar; namespace yiiunit\data\ar\sphinx;
/** /**
* Test Sphinx ActiveRecord class * Test Sphinx ActiveRecord class
......
<?php <?php
namespace yiiunit\data\sphinx\ar; namespace yiiunit\data\ar\sphinx;
use yii\sphinx\ActiveRelation; use yii\sphinx\ActiveRelation;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
......
<?php <?php
namespace yiiunit\data\sphinx\ar; namespace yiiunit\data\ar\sphinx;
use yii\db\ActiveRelation; use yii\db\ActiveRelation;
......
<?php <?php
namespace yiiunit\data\sphinx\ar; namespace yiiunit\data\ar\sphinx;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
......
<?php <?php
namespace yiiunit\data\sphinx\ar; namespace yiiunit\data\ar\sphinx;
class ItemIndex extends ActiveRecord class ItemIndex extends ActiveRecord
{ {
......
<?php <?php
namespace yiiunit\data\sphinx\ar; namespace yiiunit\data\ar\sphinx;
class RuntimeIndex extends ActiveRecord class RuntimeIndex extends ActiveRecord
{ {
......
<?php <?php
namespace yiiunit\data\sphinx\ar; namespace yiiunit\data\ar\sphinx;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
......
...@@ -4,8 +4,8 @@ namespace yiiunit\extensions\sphinx; ...@@ -4,8 +4,8 @@ namespace yiiunit\extensions\sphinx;
use yii\data\ActiveDataProvider; use yii\data\ActiveDataProvider;
use yii\sphinx\Query; use yii\sphinx\Query;
use yiiunit\data\sphinx\ar\ActiveRecord; use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\sphinx\ar\ArticleIndex; use yiiunit\data\ar\sphinx\ArticleIndex;
/** /**
* @group sphinx * @group sphinx
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
namespace yiiunit\extensions\sphinx; namespace yiiunit\extensions\sphinx;
use yii\sphinx\ActiveQuery; use yii\sphinx\ActiveQuery;
use yiiunit\data\sphinx\ar\ActiveRecord; use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\sphinx\ar\ArticleIndex; use yiiunit\data\ar\sphinx\ArticleIndex;
use yiiunit\data\sphinx\ar\RuntimeIndex; use yiiunit\data\ar\sphinx\RuntimeIndex;
/** /**
* @group sphinx * @group sphinx
...@@ -41,10 +41,9 @@ class ActiveRecordTest extends SphinxTestCase ...@@ -41,10 +41,9 @@ class ActiveRecordTest extends SphinxTestCase
$this->assertTrue($articles[1] instanceof ArticleIndex); $this->assertTrue($articles[1] instanceof ArticleIndex);
// find fulltext // find fulltext
$articles = ArticleIndex::find('cats'); $article = ArticleIndex::find(2);
$this->assertEquals(1, count($articles)); $this->assertTrue($article instanceof ArticleIndex);
$this->assertTrue($articles[0] instanceof ArticleIndex); $this->assertEquals(2, $article->id);
$this->assertEquals(1, $articles[0]->id);
// find by column values // find by column values
$article = ArticleIndex::find(['id' => 2, 'author_id' => 2]); $article = ArticleIndex::find(['id' => 2, 'author_id' => 2]);
...@@ -168,7 +167,7 @@ class ActiveRecordTest extends SphinxTestCase ...@@ -168,7 +167,7 @@ class ActiveRecordTest extends SphinxTestCase
$record = RuntimeIndex::find(['id' => 2]); $record = RuntimeIndex::find(['id' => 2]);
$record->content = 'Test content with ' . $query; $record->content = 'Test content with ' . $query;
$record->save(); $record->save();
$rows = RuntimeIndex::find($query); $rows = RuntimeIndex::find()->match($query);
$this->assertNotEmpty($rows); $this->assertNotEmpty($rows);
// updateAll // updateAll
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
namespace yiiunit\extensions\sphinx; namespace yiiunit\extensions\sphinx;
use yiiunit\data\sphinx\ar\ActiveRecord; use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
use yiiunit\data\sphinx\ar\ArticleIndex; use yiiunit\data\ar\sphinx\ArticleIndex;
use yiiunit\data\sphinx\ar\ArticleDb; use yiiunit\data\ar\sphinx\ArticleDb;
/** /**
* @group sphinx * @group sphinx
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
namespace yiiunit\extensions\sphinx; namespace yiiunit\extensions\sphinx;
use yiiunit\data\sphinx\ar\ActiveRecord; use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
use yiiunit\data\sphinx\ar\ArticleIndex; use yiiunit\data\ar\sphinx\ArticleIndex;
use yiiunit\data\sphinx\ar\ArticleDb; use yiiunit\data\ar\sphinx\ArticleDb;
use yiiunit\data\sphinx\ar\TagDb; use yiiunit\data\ar\sphinx\TagDb;
/** /**
* @group sphinx * @group sphinx
......
...@@ -303,4 +303,21 @@ class ArrayHelperTest extends TestCase ...@@ -303,4 +303,21 @@ class ArrayHelperTest extends TestCase
], ],
], $result); ], $result);
} }
public function testKeyExists()
{
$array = [
'a' => 1,
'B' => 2,
];
$this->assertTrue(ArrayHelper::keyExists('a', $array));
$this->assertFalse(ArrayHelper::keyExists('b', $array));
$this->assertTrue(ArrayHelper::keyExists('B', $array));
$this->assertFalse(ArrayHelper::keyExists('c', $array));
$this->assertTrue(ArrayHelper::keyExists('a', $array, false));
$this->assertTrue(ArrayHelper::keyExists('b', $array, false));
$this->assertTrue(ArrayHelper::keyExists('B', $array, false));
$this->assertFalse(ArrayHelper::keyExists('c', $array, false));
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment