Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Rotua Panjaitan
yii2
Commits
b94400ba
Commit
b94400ba
authored
May 13, 2013
by
Qiang Xue
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #253 from resurtm/ar-transactions-3
Fixes #226: atomic operations and transaction support in AR.
parents
31a46f5b
4973838b
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
203 additions
and
70 deletions
+203
-70
ActiveRecord.md
docs/api/db/ActiveRecord.md
+4
-0
ar.md
docs/internals/ar.md
+21
-4
Model.php
yii/base/Model.php
+23
-16
ActiveRecord.php
yii/db/ActiveRecord.php
+155
-50
No files found.
docs/api/db/ActiveRecord.md
View file @
b94400ba
...
...
@@ -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
docs/internals/ar.md
View file @
b94400ba
ActiveRecord
============
Scenarios
---------
All 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
yii/base/Model.php
View file @
b94400ba
...
...
@@ -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
;
}
/**
...
...
yii/db/ActiveRecord.php
View file @
b94400ba
...
...
@@ -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
fals
e
;
}
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
;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment