Commit e35f1354 by Qiang Xue

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

parents f1316131 6431019a
......@@ -27,7 +27,7 @@ $this->registerAssetBundle('app');
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<?php echo Menu::widget($this, array(
<?php echo Menu::widget(array(
'options' => array('class' => 'nav'),
'items' => array(
array('label' => 'Home', 'url' => array('/site/index')),
......@@ -44,7 +44,7 @@ $this->registerAssetBundle('app');
<!-- /.navbar -->
</div>
<?php echo Breadcrumbs::widget($this, array(
<?php echo Breadcrumbs::widget(array(
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(),
)); ?>
<?php echo $content; ?>
......@@ -60,7 +60,7 @@ $this->registerAssetBundle('app');
</div>
<?php $this->endBody(); ?>
</div>
<?php echo Toolbar::widget($this); ?>
<?php echo Toolbar::widget(); ?>
</body>
</html>
<?php $this->endPage(); ?>
......@@ -23,7 +23,7 @@ $this->params['breadcrumbs'][] = $this->title;
If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
</p>
<?php $form = ActiveForm::begin($this, array(
<?php $form = ActiveForm::begin(array(
'options' => array('class' => 'form-horizontal'),
'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')),
)); ?>
......@@ -35,7 +35,7 @@ $this->params['breadcrumbs'][] = $this->title;
$field = $form->field($model, 'verifyCode');
echo $field->begin()
. $field->label()
. Captcha::widget($this)
. Captcha::widget()
. Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium'))
. $field->error()
. $field->end();
......
......@@ -14,7 +14,7 @@ $this->params['breadcrumbs'][] = $this->title;
<p>Please fill out the following fields to login:</p>
<?php $form = ActiveForm::begin($this, array('options' => array('class' => 'form-horizontal'))); ?>
<?php $form = ActiveForm::begin(array('options' => array('class' => 'form-horizontal'))); ?>
<?php echo $form->field($model, 'username')->textInput(); ?>
<?php echo $form->field($model, 'password')->passwordInput(); ?>
<?php echo $form->field($model, 'rememberMe')->checkbox(); ?>
......
......@@ -446,3 +446,7 @@ $customers = Customer::find()->olderThan(50)->all();
The parameters should follow after the `$query` parameter when defining the scope method, and they
can take default values like shown above.
### Atomic operations and scenarios
TBD
......@@ -218,7 +218,7 @@ methods of the `Widget` class. For example,
```php
// $this refers to the View object
// Note that you have to "echo" the result to display it
echo \yii\widgets\Menu::widget($this, array('items' => $items));
echo \yii\widgets\Menu::widget(array('items' => $items));
// $this refers to the View object
$form = \yii\widgets\ActiveForm::begin($this);
......
ActiveRecord
============
Scenarios
---------
Possible scenario formats supported by ActiveRecord:
```php
public function scenarios()
{
return array(
// attributes array, all operations won't be wrapped with transaction
'scenario1' => array('attribute1', 'attribute2'),
// insert and update operations will be wrapped with transaction, delete won't be wrapped
'scenario2' => array(
'attributes' => array('attribute1', 'attribute2'),
'atomic' => array(self::OP_INSERT, self::OP_UPDATE),
),
);
}
```
Query
-----
### Basic Queries
### Relational Queries
### Scopes
......@@ -57,7 +57,7 @@
// whether to perform validation when a change is detected on the input
validateOnChange: false,
// whether to perform validation when the user is typing.
validateOnType: false,
validateOnType: false,
// number of milliseconds that the validation should be delayed when a user is typing in the input field.
validationDelay: 200,
// whether to enable AJAX-based validation.
......
......@@ -51,8 +51,8 @@
dataType: 'json',
cache: false,
success: function(data) {
$e.attr('src', data['url']);
$('body').data(settings.hashKey, [data['hash1'], data['hash2']]);
$e.attr('src', data.url);
$('body').data(settings.hashKey, [data.hash1, data.hash2]);
}
});
},
......
......@@ -410,6 +410,7 @@ class Controller extends Component
* 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()
......
......@@ -590,18 +590,22 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
/**
* Returns the attribute names that are safe to be massively assigned in the current scenario.
* @return array safe attribute names
* @return string[] safe attribute names
*/
public function safeAttributes()
{
$scenario = $this->getScenario();
$scenarios = $this->scenarios();
if (!isset($scenarios[$scenario])) {
return array();
}
$attributes = array();
if (isset($scenarios[$scenario])) {
foreach ($scenarios[$scenario] as $attribute) {
if ($attribute[0] !== '!') {
$attributes[] = $attribute;
}
if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) {
$scenarios[$scenario] = $scenarios[$scenario]['attributes'];
}
foreach ($scenarios[$scenario] as $attribute) {
if ($attribute[0] !== '!') {
$attributes[] = $attribute;
}
}
return $attributes;
......@@ -609,23 +613,26 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
/**
* Returns the attribute names that are subject to validation in the current scenario.
* @return array safe attribute names
* @return string[] safe attribute names
*/
public function activeAttributes()
{
$scenario = $this->getScenario();
$scenarios = $this->scenarios();
if (isset($scenarios[$scenario])) {
$attributes = $scenarios[$this->getScenario()];
foreach ($attributes as $i => $attribute) {
if ($attribute[0] === '!') {
$attributes[$i] = substr($attribute, 1);
}
}
return $attributes;
} else {
if (!isset($scenarios[$scenario])) {
return array();
}
if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) {
$attributes = $scenarios[$scenario]['attributes'];
} else {
$attributes = $scenarios[$scenario];
}
foreach ($attributes as $i => $attribute) {
if ($attribute[0] === '!') {
$attributes[$i] = substr($attribute, 1);
}
}
return $attributes;
}
/**
......
......@@ -449,7 +449,7 @@ abstract class Module extends Component
public function getComponent($id, $load = true)
{
if (isset($this->_components[$id])) {
if ($this->_components[$id] instanceof Component) {
if ($this->_components[$id] instanceof Object) {
return $this->_components[$id];
} elseif ($load) {
Yii::trace("Loading component: $id", __METHOD__);
......
......@@ -373,9 +373,10 @@ class View extends Component
*/
public function beginBlock($id, $renderInPlace = false)
{
return Block::begin($this, array(
return Block::begin(array(
'id' => $id,
'renderInPlace' => $renderInPlace,
'view' => $this,
));
}
......@@ -406,9 +407,10 @@ class View extends Component
*/
public function beginContent($viewFile, $params = array())
{
return ContentDecorator::begin($this, array(
return ContentDecorator::begin(array(
'viewFile' => $viewFile,
'params' => $params,
'view' => $this,
));
}
......@@ -442,8 +444,9 @@ class View extends Component
public function beginCache($id, $properties = array())
{
$properties['id'] = $id;
$properties['view'] = $this;
/** @var $cache FragmentCache */
$cache = FragmentCache::begin($this, $properties);
$cache = FragmentCache::begin($properties);
if ($cache->getCachedContent() !== false) {
$this->endCache();
return false;
......
......@@ -18,16 +18,6 @@ use Yii;
class Widget extends Component
{
/**
* @var View the view object that this widget is associated with.
* The widget will use this view object to register any needed assets.
* This property is also required by [[render()]] and [[renderFile()]].
*/
public $view;
/**
* @var string id of the widget.
*/
private $_id;
/**
* @var integer a counter used to generate [[id]] for widgets.
* @internal
*/
......@@ -39,32 +29,19 @@ class Widget extends Component
*/
public static $_stack = array();
/**
* Constructor.
* @param View $view the view object that this widget is associated with.
* The widget will use this view object to register any needed assets.
* It is also required by [[render()]] and [[renderFile()]].
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($view, $config = array())
{
$this->view = $view;
parent::__construct($config);
}
/**
* 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 View $view the view object that the newly created widget is associated with.
* @param array $config name-value pairs that will be used to initialize the object properties
* @return Widget the newly created widget instance
*/
public static function begin($view, $config = array())
public static function begin($config = array())
{
$config['class'] = get_called_class();
/** @var Widget $widget */
$widget = Yii::createObject($config, $view);
$widget = Yii::createObject($config);
self::$_stack[] = $widget;
return $widget;
}
......@@ -93,21 +70,22 @@ class Widget extends Component
/**
* Creates a widget instance and runs it.
* The widget rendering result is returned by this method.
* @param View $view the view object that the newly created widget is associated with.
* @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($view, $config = array())
public static function widget($config = array())
{
ob_start();
ob_implicit_flush(false);
/** @var Widget $widget */
$config['class'] = get_called_class();
$widget = Yii::createObject($config, $view);
$widget = Yii::createObject($config);
$widget->run();
return ob_get_clean();
}
private $_id;
/**
* Returns the ID of the widget.
* @param boolean $autoGenerate whether to generate an ID if it is not set previously
......@@ -130,6 +108,32 @@ class Widget extends Component
$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 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.
*/
......@@ -159,7 +163,7 @@ class Widget extends Component
public function render($view, $params = array())
{
$viewFile = $this->findViewFile($view);
return $this->view->renderFile($viewFile, $params, $this);
return $this->getView()->renderFile($viewFile, $params, $this);
}
/**
......@@ -171,7 +175,7 @@ class Widget extends Component
*/
public function renderFile($file, $params = array())
{
return $this->view->renderFile($file, $params, $this);
return $this->getView()->renderFile($file, $params, $this);
}
/**
......
......@@ -74,6 +74,22 @@ class ActiveRecord extends Model
const EVENT_AFTER_DELETE = 'afterDelete';
/**
* Represents insert ActiveRecord operation. This constant is used for specifying set of atomic operations
* for particular scenario in the [[scenarios()]] method.
*/
const OP_INSERT = 'insert';
/**
* Represents update ActiveRecord operation. This constant is used for specifying set of atomic operations
* for particular scenario in the [[scenarios()]] method.
*/
const OP_UPDATE = 'update';
/**
* Represents delete ActiveRecord operation. This constant is used for specifying set of atomic operations
* for particular scenario in the [[scenarios()]] method.
*/
const OP_DELETE = 'delete';
/**
* @var array attribute values indexed by attribute names
*/
private $_attributes = array();
......@@ -664,10 +680,39 @@ class ActiveRecord extends Model
* @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) || !$this->beforeSave(true)) {
if ($runValidation && !$this->validate($attributes)) {
return false;
}
$db = static::getDb();
$transaction = $this->isOperationAtomic(self::OP_INSERT) && $db->getTransaction() === null ? $db->beginTransaction() : null;
try {
$result = $this->insertInternal($attributes);
if ($transaction !== null) {
if ($result === false) {
$transaction->rollback();
} else {
$transaction->commit();
}
}
} catch (\Exception $e) {
if ($transaction !== null) {
$transaction->rollback();
}
throw $e;
}
return $result;
}
/**
* @see ActiveRecord::insert()
*/
private function insertInternal($attributes = null)
{
if (!$this->beforeSave(true)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
......@@ -678,22 +723,23 @@ class ActiveRecord extends Model
}
$db = static::getDb();
$command = $db->createCommand()->insert($this->tableName(), $values);
if ($command->execute()) {
$table = $this->getTableSchema();
if ($table->sequenceName !== null) {
foreach ($table->primaryKey as $name) {
if (!isset($this->_attributes[$name])) {
$this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName);
break;
}
if (!$command->execute()) {
return false;
}
$table = $this->getTableSchema();
if ($table->sequenceName !== null) {
foreach ($table->primaryKey as $name) {
if (!isset($this->_attributes[$name])) {
$this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName);
break;
}
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $value;
}
$this->afterSave(true);
return true;
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $value;
}
$this->afterSave(true);
return true;
}
/**
......@@ -744,39 +790,67 @@ class ActiveRecord extends Model
* 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) || !$this->beforeSave(false)) {
if ($runValidation && !$this->validate($attributes)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (!empty($values)) {
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
if (!isset($values[$lock])) {
$values[$lock] = $this->$lock + 1;
$db = static::getDb();
$transaction = $this->isOperationAtomic(self::OP_UPDATE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
try {
$result = $this->updateInternal($attributes);
if ($transaction !== null) {
if ($result === false) {
$transaction->rollback();
} else {
$transaction->commit();
}
$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.');
} catch (\Exception $e) {
if ($transaction !== null) {
$transaction->rollback();
}
throw $e;
}
return $result;
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
/**
* @see CActiveRecord::update()
* @throws StaleObjectException
*/
private function updateInternal($attributes = null)
{
if (!$this->beforeSave(false)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
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);
$this->afterSave(false);
return $rows;
} else {
return 0;
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;
}
/**
......@@ -826,27 +900,43 @@ class ActiveRecord extends Model
* 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()
{
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;
$db = static::getDb();
$transaction = $this->isOperationAtomic(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->_oldAttributes = null;
$this->afterDelete();
}
$rows = $this->deleteAll($condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being deleted is outdated.');
if ($transaction !== null) {
if ($result === false) {
$transaction->rollback();
} else {
$transaction->commit();
}
}
$this->_oldAttributes = null;
$this->afterDelete();
return $rows;
} else {
return false;
} catch (\Exception $e) {
if ($transaction !== null) {
$transaction->rollback();
}
throw $e;
}
return $result;
}
/**
......@@ -1336,4 +1426,19 @@ class ActiveRecord extends Model
}
return true;
}
/**
* @param string $operation possible values are ActiveRecord::INSERT, ActiveRecord::UPDATE and ActiveRecord::DELETE.
* @return boolean whether given operation is atomic. Currently active scenario is taken into account.
*/
private function isOperationAtomic($operation)
{
$scenario = $this->getScenario();
$scenarios = $this->scenarios();
if (isset($scenarios[$scenario], $scenario[$scenario]['atomic']) && is_array($scenarios[$scenario]['atomic'])) {
return in_array($operation, $scenarios[$scenario]['atomic']);
} else {
return false;
}
}
}
......@@ -260,10 +260,6 @@ class CaptchaAction extends Action
(int)($this->foreColor % 0x10000 / 0x100),
$this->foreColor % 0x100);
if ($this->fontFile === null) {
$this->fontFile = dirname(__FILE__) . '/SpicyRice.ttf';
}
$length = strlen($code);
$box = imagettfbbox(30, 0, $this->fontFile, $code);
$w = $box[4] - $box[0] + $this->offset * ($length - 1);
......@@ -302,10 +298,6 @@ class CaptchaAction extends Action
$image = new \Imagick();
$image->newImage($this->width, $this->height, $backColor);
if ($this->fontFile === null) {
$this->fontFile = dirname(__FILE__) . '/SpicyRice.ttf';
}
$draw = new \ImagickDraw();
$draw->setFont($this->fontFile);
$draw->setFontSize(30);
......
......@@ -47,7 +47,7 @@ use Yii;
* }
*
* // display pagination
* LinkPager::widget($this, array(
* LinkPager::widget(array(
* 'pages' => $pages,
* ));
* ~~~
......
......@@ -23,7 +23,7 @@ use yii\helpers\Html;
*
* ~~~
* // $this is the view object currently being used
* echo Breadcrumbs::widget($this, array(
* echo Breadcrumbs::widget(array(
* 'links' => array(
* array('label' => 'Sample Post', 'url' => array('post/edit', 'id' => 1)),
* 'Edit',
......@@ -37,7 +37,7 @@ use yii\helpers\Html;
*
* ~~~
* // $this is the view object currently being used
* echo Breadcrumbs::widget($this, array(
* echo Breadcrumbs::widget(array(
* 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(),
* ));
* ~~~
......
......@@ -27,7 +27,7 @@ use yii\helpers\Html;
*
* ~~~
* // $this is the view object currently being used
* echo Menu::widget($this, array(
* echo Menu::widget(array(
* 'items' => array(
* // Important: you need to specify url as 'controller/action',
* // not just as 'controller' even if default action is used.
......
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