Merge branch 'master' into 2.6

Conflicts:
	lib/Cake/Model/Model.php
This commit is contained in:
mark_story 2014-08-08 23:28:06 -04:00
commit 9c3089796f
15 changed files with 650 additions and 212 deletions

View file

@ -1,13 +1,16 @@
CakePHP
=======
# CakePHP
[![Bake Status](https://secure.travis-ci.org/cakephp/cakephp.png?branch=master)](http://travis-ci.org/cakephp/cakephp)
[![Latest Stable Version](https://poser.pugx.org/cakephp/cakephp/v/stable.svg)](https://packagist.org/packages/cakephp/cakephp)
[![License](https://poser.pugx.org/cakephp/cakephp/license.svg)](https://packagist.org/packages/cakephp/cakephp)
[![CakePHP](http://cakephp.org/img/cake-logo.png)](http://www.cakephp.org)
CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC.
Our primary goal is to provide a structured framework that enables PHP users at all levels to rapidly develop robust web applications, without any loss to flexibility.
Some Handy Links
----------------
## Some Handy Links
[CakePHP](http://www.cakephp.org) - The rapid development PHP framework
@ -27,8 +30,8 @@ Some Handy Links
[Cake Software Foundation](http://cakefoundation.org) - Promoting development related to CakePHP
Get Support!
------------
## Get Support!
[#cakephp](http://webchat.freenode.net/?channels=#cakephp) on irc.freenode.net - Come chat with us, we have cake
@ -38,6 +41,3 @@ Get Support!
[Roadmaps](https://github.com/cakephp/cakephp/wiki#roadmaps) - Want to contribute? Get involved!
[![Bake Status](https://secure.travis-ci.org/cakephp/cakephp.png?branch=master)](http://travis-ci.org/cakephp/cakephp)
![Cake Power](https://raw.github.com/cakephp/cakephp/master/lib/Cake/Console/Templates/skel/webroot/img/cake.power.gif)

View file

@ -411,7 +411,7 @@ class ExtractTask extends AppShell {
}
list($type, $string, $line) = $countToken;
if (($type == T_STRING) && ($string == $functionName) && ($firstParenthesis === '(')) {
if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) {
$position = $count;
$depth = 0;

View file

@ -284,7 +284,7 @@ class CookieComponent extends Component {
return null;
}
if (!empty($names[1])) {
if (!empty($names[1]) && is_array($this->_values[$this->name][$key])) {
return Hash::get($this->_values[$this->name][$key], $names[1]);
}
return $this->_values[$this->name][$key];

View file

@ -70,6 +70,7 @@ App::uses('LogEngineCollection', 'Log');
* on scopes
*
* @package Cake.Log
* @link http://book.cakephp.org/2.0/en/core-libraries/logging.html#logging
*/
class CakeLog {
@ -183,6 +184,7 @@ class CakeLog {
* @param array $config Array of configuration information for the logger
* @return bool success of configuration.
* @throws CakeLogException
* @link http://book.cakephp.org/2.0/en/core-libraries/logging.html#creating-and-configuring-log-streams
*/
public static function config($key, $config) {
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $key)) {
@ -398,6 +400,7 @@ class CakeLog {
* @param string|array $scope The scope(s) a log message is being created in.
* See CakeLog::config() for more information on logging scopes.
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-libraries/logging.html#writing-to-logs
*/
public static function write($type, $message, $scope = array()) {
if (empty(self::$_Collection)) {

View file

@ -348,7 +348,7 @@ class CakeSession {
$config = self::read('Config');
$validAgent = (
Configure::read('Session.checkAgent') === false ||
self::$_userAgent == $config['userAgent']
isset($config['userAgent']) && self::$_userAgent === $config['userAgent']
);
return ($validAgent && self::$time <= $config['time']);
}

View file

@ -1316,6 +1316,7 @@ class DboSource extends DataSource {
foreach ($resultSet as &$row) {
if ($type === 'hasOne' || $type === 'belongsTo' || $type === 'hasMany') {
$assocResultSet = array();
$prefetched = false;
if (
($type === 'hasOne' || $type === 'belongsTo') &&
@ -1326,6 +1327,7 @@ class DboSource extends DataSource {
if (!empty($joinedData)) {
$assocResultSet[0] = array($LinkModel->alias => $row[$LinkModel->alias]);
}
$prefetched = true;
} else {
$query = $this->insertQueryData($queryTemplate, $row, $association, $Model, $stack);
if ($query !== false) {
@ -1376,7 +1378,7 @@ class DboSource extends DataSource {
$this->_mergeAssociation($row, $assocResultSet, $association, $type, $selfJoin);
}
if ($type !== 'hasAndBelongsToMany' && isset($row[$association])) {
if ($type !== 'hasAndBelongsToMany' && isset($row[$association]) && !$prefetched) {
$row[$association] = $LinkModel->afterFind($row[$association], false);
}
@ -1863,7 +1865,7 @@ class DboSource extends DataSource {
'type' => null,
'alias' => null,
'table' => 'join_table',
'conditions' => array()
'conditions' => '',
), $join);
if (!empty($data['alias'])) {
@ -1917,7 +1919,7 @@ class DboSource extends DataSource {
* @return string
*/
public function renderJoinStatement($data) {
if (strtoupper($data['type']) === 'CROSS') {
if (strtoupper($data['type']) === 'CROSS' || empty($data['conditions'])) {
return "{$data['type']} JOIN {$data['table']} {$data['alias']}";
}
return trim("{$data['type']} JOIN {$data['table']} {$data['alias']} ON ({$data['conditions']})");

View file

@ -2264,6 +2264,7 @@ class Model extends Object implements CakeEventListener {
* @return mixed If atomic: True on success, or false on failure.
* Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record saved successfully.
* @throws PDOException
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savemany-array-data-null-array-options-array
*/
public function saveMany($data = null, $options = array()) {
@ -2291,50 +2292,60 @@ class Model extends Object implements CakeEventListener {
$options['validate'] = false;
}
$transactionBegun = false;
if ($options['atomic']) {
$db = $this->getDataSource();
$transactionBegun = $db->begin();
}
$return = array();
foreach ($data as $key => $record) {
$validates = $this->create(null) !== null;
$saved = false;
if ($validates) {
if ($options['deep']) {
$saved = $this->saveAssociated($record, array('atomic' => false) + $options);
} else {
$saved = $this->save($record, array('atomic' => false) + $options);
try {
$return = array();
foreach ($data as $key => $record) {
$validates = $this->create(null) !== null;
$saved = false;
if ($validates) {
if ($options['deep']) {
$saved = $this->saveAssociated($record, array('atomic' => false) + $options);
} else {
$saved = $this->save($record, array('atomic' => false) + $options);
}
}
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
if (!$validates) {
$validationErrors[$key] = $this->validationErrors;
}
if (!$options['atomic']) {
$return[$key] = $validates;
} elseif (!$validates) {
break;
}
}
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
if (!$validates) {
$validationErrors[$key] = $this->validationErrors;
}
$this->validationErrors = $validationErrors;
if (!$options['atomic']) {
$return[$key] = $validates;
} elseif (!$validates) {
break;
return $return;
}
}
$this->validationErrors = $validationErrors;
if ($validates) {
if ($transactionBegun) {
return $db->commit() !== false;
}
return true;
}
if (!$options['atomic']) {
return $return;
}
if ($validates) {
if ($transactionBegun) {
return $db->commit() !== false;
$db->rollback();
}
return true;
return false;
} catch (Exception $e) {
if ($transactionBegun) {
$db->rollback();
}
throw $e;
}
$db->rollback();
return false;
}
/**
@ -2386,6 +2397,7 @@ class Model extends Object implements CakeEventListener {
* @return mixed If atomic: True on success, or false on failure.
* Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record saved successfully.
* @throws PDOException
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
*/
public function saveAssociated($data = null, $options = array()) {
@ -2414,136 +2426,146 @@ class Model extends Object implements CakeEventListener {
$options['validate'] = false;
}
$transactionBegun = false;
if ($options['atomic']) {
$db = $this->getDataSource();
$transactionBegun = $db->begin();
}
$associations = $this->getAssociated();
$return = array();
$validates = true;
foreach ($data as $association => $values) {
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
if ($isEmpty || !isset($associations[$association]) || $associations[$association] !== 'belongsTo') {
continue;
}
$Model = $this->{$association};
$validates = $Model->create(null) !== null;
$saved = false;
if ($validates) {
if ($options['deep']) {
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
} else {
$saved = $Model->save($values, array('atomic' => false) + $options);
try {
$associations = $this->getAssociated();
$return = array();
$validates = true;
foreach ($data as $association => $values) {
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
if ($isEmpty || !isset($associations[$association]) || $associations[$association] !== 'belongsTo') {
continue;
}
$validates = ($saved === true || (is_array($saved) && !in_array(false, $saved, true)));
}
if ($validates) {
$key = $this->belongsTo[$association]['foreignKey'];
if (isset($data[$this->alias])) {
$data[$this->alias][$key] = $Model->id;
} else {
$data = array_merge(array($key => $Model->id), $data, array($key => $Model->id));
}
$options = $this->_addToWhiteList($key, $options);
} else {
$validationErrors[$association] = $Model->validationErrors;
}
$Model = $this->{$association};
$return[$association] = $validates;
}
if ($validates && !($this->create(null) !== null && $this->save($data, array('atomic' => false) + $options))) {
$validationErrors[$this->alias] = $this->validationErrors;
$validates = false;
}
$return[$this->alias] = $validates;
foreach ($data as $association => $values) {
if (!$validates) {
break;
}
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
if ($isEmpty || !isset($associations[$association])) {
continue;
}
$Model = $this->{$association};
$type = $associations[$association];
$key = $this->{$type}[$association]['foreignKey'];
switch ($type) {
case 'hasOne':
if (isset($values[$association])) {
$values[$association][$key] = $this->id;
$validates = $Model->create(null) !== null;
$saved = false;
if ($validates) {
if ($options['deep']) {
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
} else {
$values = array_merge(array($key => $this->id), $values, array($key => $this->id));
$saved = $Model->save($values, array('atomic' => false) + $options);
}
$validates = ($saved === true || (is_array($saved) && !in_array(false, $saved, true)));
}
$validates = $Model->create(null) !== null;
$saved = false;
if ($validates) {
$key = $this->belongsTo[$association]['foreignKey'];
if (isset($data[$this->alias])) {
$data[$this->alias][$key] = $Model->id;
} else {
$data = array_merge(array($key => $Model->id), $data, array($key => $Model->id));
}
$options = $this->_addToWhiteList($key, $options);
} else {
$validationErrors[$association] = $Model->validationErrors;
}
$return[$association] = $validates;
}
if ($validates && !($this->create(null) !== null && $this->save($data, array('atomic' => false) + $options))) {
$validationErrors[$this->alias] = $this->validationErrors;
$validates = false;
}
$return[$this->alias] = $validates;
foreach ($data as $association => $values) {
if (!$validates) {
break;
}
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
if ($isEmpty || !isset($associations[$association])) {
continue;
}
$Model = $this->{$association};
$type = $associations[$association];
$key = $this->{$type}[$association]['foreignKey'];
switch ($type) {
case 'hasOne':
if (isset($values[$association])) {
$values[$association][$key] = $this->id;
} else {
$values = array_merge(array($key => $this->id), $values, array($key => $this->id));
}
$validates = $Model->create(null) !== null;
$saved = false;
if ($validates) {
$options = $Model->_addToWhiteList($key, $options);
if ($options['deep']) {
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
} else {
$saved = $Model->save($values, $options);
}
}
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
if (!$validates) {
$validationErrors[$association] = $Model->validationErrors;
}
$return[$association] = $validates;
break;
case 'hasMany':
foreach ($values as $i => $value) {
if (isset($values[$i][$association])) {
$values[$i][$association][$key] = $this->id;
} else {
$values[$i] = array_merge(array($key => $this->id), $value, array($key => $this->id));
}
}
if ($validates) {
$options = $Model->_addToWhiteList($key, $options);
if ($options['deep']) {
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
} else {
$saved = $Model->save($values, array('atomic' => false) + $options);
$_return = $Model->saveMany($values, array('atomic' => false) + $options);
if (in_array(false, $_return, true)) {
$validationErrors[$association] = $Model->validationErrors;
$validates = false;
}
}
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
if (!$validates) {
$validationErrors[$association] = $Model->validationErrors;
}
$return[$association] = $validates;
break;
case 'hasMany':
foreach ($values as $i => $value) {
if (isset($values[$i][$association])) {
$values[$i][$association][$key] = $this->id;
} else {
$values[$i] = array_merge(array($key => $this->id), $value, array($key => $this->id));
}
}
$options = $Model->_addToWhiteList($key, $options);
$_return = $Model->saveMany($values, array('atomic' => false) + $options);
if (in_array(false, $_return, true)) {
$validationErrors[$association] = $Model->validationErrors;
$validates = false;
}
$return[$association] = $_return;
break;
$return[$association] = $_return;
break;
}
}
}
$this->validationErrors = $validationErrors;
$this->validationErrors = $validationErrors;
if (isset($validationErrors[$this->alias])) {
$this->validationErrors = $validationErrors[$this->alias];
unset($validationErrors[$this->alias]);
$this->validationErrors = array_merge($this->validationErrors, $validationErrors);
}
if (isset($validationErrors[$this->alias])) {
$this->validationErrors = $validationErrors[$this->alias];
unset($validationErrors[$this->alias]);
$this->validationErrors = array_merge($this->validationErrors, $validationErrors);
}
if (!$options['atomic']) {
return $return;
}
if ($validates) {
if ($transactionBegun) {
return $db->commit() !== false;
}
return true;
}
if (!$options['atomic']) {
return $return;
}
if ($validates) {
if ($transactionBegun) {
return $db->commit() !== false;
$db->rollback();
}
return true;
return false;
} catch (Exception $e) {
if ($transactionBegun) {
$db->rollback();
}
throw $e;
}
$db->rollback();
return false;
}
/**

View file

@ -601,6 +601,7 @@ class CakeResponse {
return isset($headers['Location']) ? $headers['Location'] : null;
}
$this->header('Location', $url);
return null;
}
/**

View file

@ -1197,6 +1197,11 @@ class DboSourceTest extends CakeTestCase {
'table' => 'posts_tags',
'conditions' => array('1 = 1')
), 'CROSS JOIN cakephp.posts_tags AS PostsTag'),
array(array(
'type' => 'LEFT',
'alias' => 'PostsTag',
'table' => 'posts_tags',
), 'LEFT JOIN cakephp.posts_tags AS PostsTag'),
array(array(
'type' => 'LEFT',
'alias' => 'PostsTag',
@ -1456,4 +1461,51 @@ class DboSourceTest extends CakeTestCase {
$result = $db->defaultConditions($Article, null);
$this->assertFalse($result);
}
/**
* Test that count how many times is afterFind called
*
* @return void
*/
public function testCountAfterFindCalls() {
$this->loadFixtures('Article', 'User', 'Comment', 'Attachment', 'Tag', 'ArticlesTag');
// Use alias to make testing "primary = true" easy
$Primary = $this->getMock('Comment', array('afterFind'), array(array('alias' => 'Primary')), '', true);
$Primary->expects($this->any())->method('afterFind')->will($this->returnArgument(0));
$Article = $this->getMock('Article', array('afterFind'), array(), '', true);
$User = $this->getMock('User', array('afterFind'), array(), '', true);
$Comment = $this->getMock('Comment', array('afterFind'), array(), '', true);
$Tag = $this->getMock('Tag', array('afterFind'), array(), '', true);
$Attachment = $this->getMock('Attachment', array('afterFind'), array(), '', true);
$Primary->Article = $Article;
$Primary->Article->User = $User;
$Primary->Article->Tag = $Tag;
$Primary->Article->Comment = $Comment;
$Primary->Attachment = $Attachment;
$Primary->Attachment->Comment = $Comment;
$Primary->User = $User;
// primary = true
$Primary->expects($this->once())
->method('afterFind')->with($this->anything(), $this->isTrue())->will($this->returnArgument(0));
// primary = false
$Article->expects($this->once()) // Primary belongs to 1 Article
->method('afterFind')->with($this->anything(), $this->isFalse())->will($this->returnArgument(0));
$User->expects($this->exactly(2)) // Article belongs to 1 User and Primary belongs to 1 User
->method('afterFind')->with($this->anything(), $this->isFalse())->will($this->returnArgument(0));
$Tag->expects($this->exactly(2)) // Article has 2 Tags
->method('afterFind')->with($this->anything(), $this->isFalse())->will($this->returnArgument(0));
$Comment->expects($this->exactly(3)) // Article has 2 Comments and Attachment belongs to 1 Comment
->method('afterFind')->with($this->anything(), $this->isFalse())->will($this->returnArgument(0));
$Attachment->expects($this->once()) // Primary has 1 Attachment
->method('afterFind')->with($this->anything(), $this->isFalse())->will($this->returnArgument(0));
$result = $Primary->find('first', array('conditions' => array('Primary.id' => 5), 'recursive' => 2));
$this->assertCount(2, $result['Article']['Tag']);
$this->assertCount(2, $result['Article']['Comment']);
}
}

View file

@ -36,6 +36,8 @@ class TestAuthor extends Author {
protected $_dataSourceObject;
public $dataForAfterSave;
/**
* Helper method to set a datasource object
*
@ -74,6 +76,8 @@ class TestPost extends Post {
protected $_dataSourceObject;
public $dataForAfterSave;
/**
* Helper method to set a datasource object
*
@ -4378,25 +4382,57 @@ class ModelWriteTest extends BaseModelTest {
public function testSaveAllManyRowsTransactionNoRollback() {
$this->loadFixtures('Post');
$db = $this->getMock('DboSource', array('begin', 'connect', 'rollback', 'describe'));
$db->expects($this->once())
->method('describe')
->will($this->returnValue(array()));
$db->expects($this->once())->method('rollback');
$Post = new TestPost();
$Post->setDataSourceObject($db);
$Post->validate = array(
'title' => array('rule' => array('notEmpty'))
);
// If validation error occurs, rollback() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => '')
);
$Post->saveAll($data, array('atomic' => true, 'validate' => true));
// If exception thrown, rollback() should be called too.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => 'New Fifth Post', 'body' => $db->expression('PDO_EXCEPTION()'))
);
try {
$Post->saveAll($data, array('atomic' => true, 'validate' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->once())->method('commit');
$db->expects($this->never())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => 'New Fifth Post')
);
$Post->saveAll($data, array('atomic' => true, 'validate' => true));
}
/**
@ -4405,28 +4441,22 @@ class ModelWriteTest extends BaseModelTest {
* @return void
*/
public function testSaveAllAssociatedTransactionNoRollback() {
$testDb = ConnectionManager::getDataSource('test');
$db = $this->getMock('DboSource', array('connect', 'rollback', 'describe', 'create', 'update', 'begin'));
$db->columns = $testDb->columns;
$db->expects($this->once())->method('rollback');
$db->expects($this->any())->method('describe')
->will($this->returnValue(array(
'id' => array('type' => 'integer', 'length' => 11),
'title' => array('type' => 'string'),
'body' => array('type' => 'text'),
'published' => array('type' => 'string')
)));
$this->loadFixtures('Post', 'Author');
$Post = new TestPost();
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$Post->Author->validate = array(
'user' => array('rule' => array('notEmpty'))
);
// If validation error occurs, rollback() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
@ -4439,6 +4469,55 @@ class ModelWriteTest extends BaseModelTest {
)
);
$Post->saveAll($data, array('validate' => true));
// If exception thrown, rollback() should be called too.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
'body' => $db->expression('PDO_EXCEPTION()'),
'published' => 'Y'
),
'Author' => array(
'user' => 'New user',
'password' => "sekret"
)
);
try {
$Post->saveAll($data, array('validate' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->once())->method('commit');
$db->expects($this->never())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
'body' => 'Content',
'published' => 'Y'
),
'Author' => array(
'user' => 'New user',
'password' => "sekret"
)
);
$Post->saveAll($data, array('validate' => true));
}
/**
@ -5811,25 +5890,57 @@ class ModelWriteTest extends BaseModelTest {
public function testSaveManyTransactionNoRollback() {
$this->loadFixtures('Post');
$db = $this->getMock('DboSource', array('begin', 'connect', 'rollback', 'describe'));
$db->expects($this->once())
->method('describe')
->will($this->returnValue(array()));
$db->expects($this->once())->method('rollback');
$Post = new TestPost();
$Post->setDataSourceObject($db);
$Post->validate = array(
'title' => array('rule' => array('notEmpty'))
);
// If validation error occurs, rollback() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => '')
);
$Post->saveMany($data, array('validate' => true));
// If exception thrown, rollback() should be called too.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => 'New Fifth Post', 'body' => $db->expression('PDO_EXCEPTION()'))
);
try {
$Post->saveMany($data, array('validate' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->once())->method('commit');
$db->expects($this->never())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => 'New Fifth Post')
);
$Post->saveMany($data, array('validate' => true));
}
/**
@ -5838,28 +5949,22 @@ class ModelWriteTest extends BaseModelTest {
* @return void
*/
public function testSaveAssociatedTransactionNoRollback() {
$testDb = ConnectionManager::getDataSource('test');
$db = $this->getMock('DboSource', array('connect', 'rollback', 'describe', 'create', 'begin'));
$db->columns = $testDb->columns;
$db->expects($this->once())->method('rollback');
$db->expects($this->any())->method('describe')
->will($this->returnValue(array(
'id' => array('type' => 'integer', 'length' => 11),
'title' => array('type' => 'string'),
'body' => array('type' => 'text'),
'published' => array('type' => 'string')
)));
$this->loadFixtures('Post', 'Author');
$Post = new TestPost();
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$Post->Author->validate = array(
'user' => array('rule' => array('notEmpty'))
);
// If validation error occurs, rollback() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
@ -5872,6 +5977,55 @@ class ModelWriteTest extends BaseModelTest {
)
);
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
// If exception thrown, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
'body' => $db->expression('PDO_EXCEPTION()'),
'published' => 'Y'
),
'Author' => array(
'user' => 'New user',
'password' => "sekret"
)
);
try {
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->once())->method('commit');
$db->expects($this->never())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
'body' => 'Content',
'published' => 'Y'
),
'Author' => array(
'user' => 'New user',
'password' => "sekret"
)
);
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
}
/**
@ -7569,4 +7723,203 @@ class ModelWriteTest extends BaseModelTest {
$Model = $event->subject;
$Model->getDataSource()->delete($Model, array($Model->alias . '.' . $Model->primaryKey => $Model->id));
}
/**
* Creates a convenient mock DboSource
*
* We cannot call several methods via mock DboSource, such as DboSource::value()
* because mock DboSource has no $_connection.
* This method helps us to avoid this problem.
*
* @param array $methods Configurable method names.
* @return DboSource
*/
protected function _getMockDboSource($methods = array()) {
$testDb = ConnectionManager::getDataSource('test');
$passthrough = array_diff(array('value', 'begin', 'rollback', 'commit', 'describe', 'lastInsertId', 'execute'), $methods);
$methods = array_merge($methods, $passthrough);
if (!in_array('connect', $methods)) {
$methods[] = 'connect'; // This will be called by DboSource::__construct().
}
$db = $this->getMock('DboSource', $methods);
$db->columns = $testDb->columns;
$db->startQuote = $testDb->startQuote;
$db->endQuote = $testDb->endQuote;
foreach ($passthrough as $method) {
$db->expects($this->any())
->method($method)
->will($this->returnCallback(array($testDb, $method)));
}
return $db;
}
/**
* Test that transactions behave correctly on nested saveMany calls.
*
* @return void
*/
public function testTransactionOnNestedSaveMany() {
$this->loadFixtures('Post');
$Post = new TestPost();
$Post->getEventManager()->attach(array($this, 'nestedSaveMany'), 'Model.afterSave');
// begin -> [ begin -> commit ] -> commit
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->exactly(2))->method('begin')->will($this->returnValue(true));
$db->expects($this->exactly(2))->method('commit');
$db->expects($this->never())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'Outer Post'),
);
$Post->dataForAfterSave = array(
array('author_id' => 1, 'title' => 'Inner Post'),
);
$this->assertTrue($Post->saveMany($data));
// begin -> [ begin(false) ] -> commit
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->at(0))->method('begin')->will($this->returnValue(true));
$db->expects($this->at(1))->method('begin')->will($this->returnValue(false));
$db->expects($this->once())->method('commit');
$db->expects($this->never())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'Outer Post'),
);
$Post->dataForAfterSave = array(
array('author_id' => 1, 'title' => 'Inner Post'),
);
$this->assertTrue($Post->saveMany($data));
// begin -> [ begin -> rollback ] -> rollback
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->exactly(2))->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->exactly(2))->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'Outer Post'),
);
$Post->dataForAfterSave = array(
array('author_id' => 1, 'title' => 'Inner Post', 'body' => $db->expression('PDO_EXCEPTION()')),
);
try {
$Post->saveMany($data);
$this->fail('No exception thrown');
} catch(Exception $e) {
}
}
/**
* Test that transaction behaves correctly on nested saveAssociated calls.
*
* @return void
*/
public function testTransactionOnNestedSaveAssociated() {
$this->loadFixtures('Author', 'Post');
$Author = new TestAuthor();
$Author->getEventManager()->attach(array($this, 'nestedSaveAssociated'), 'Model.afterSave');
// begin -> [ begin -> commit ] -> commit
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->exactly(2))->method('begin')->will($this->returnValue(true));
$db->expects($this->exactly(2))->method('commit');
$db->expects($this->never())->method('rollback');
$Author->setDataSourceObject($db);
$Author->Post->setDataSourceObject($db);
$data = array(
'Author' => array('user' => 'outer'),
'Post' => array(
array('title' => 'Outer Post'),
)
);
$Author->dataForAfterSave = array(
'Author' => array('user' => 'inner'),
'Post' => array(
array('title' => 'Inner Post'),
)
);
$this->assertTrue($Author->saveAssociated($data));
// begin -> [ begin(false) ] -> commit
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->at(0))->method('begin')->will($this->returnValue(true));
$db->expects($this->at(1))->method('begin')->will($this->returnValue(false));
$db->expects($this->once())->method('commit');
$db->expects($this->never())->method('rollback');
$Author->setDataSourceObject($db);
$Author->Post->setDataSourceObject($db);
$data = array(
'Author' => array('user' => 'outer'),
'Post' => array(
array('title' => 'Outer Post'),
)
);
$Author->dataForAfterSave = array(
'Author' => array('user' => 'inner'),
'Post' => array(
array('title' => 'Inner Post'),
)
);
$this->assertTrue($Author->saveAssociated($data));
// begin -> [ begin -> rollback ] -> rollback
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->exactly(2))->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->exactly(2))->method('rollback');
$Author->setDataSourceObject($db);
$Author->Post->setDataSourceObject($db);
$data = array(
'Author' => array('user' => 'outer'),
'Post' => array(
array('title' => 'Outer Post'),
)
);
$Author->dataForAfterSave = array(
'Author' => array('user' => 'inner', 'password' => $db->expression('PDO_EXCEPTION()')),
'Post' => array(
array('title' => 'Inner Post'),
)
);
try {
$Author->saveAssociated($data);
$this->fail('No exception thrown');
} catch(Exception $e) {
}
}
/**
* A callback for testing nested saveMany.
*
* @param CakeEvent $event containing the Model
* @return void
*/
public function nestedSaveMany($event) {
$Model = $event->subject;
$Model->saveMany($Model->dataForAfterSave, array('callbacks' => false));
}
/**
* A callback for testing nested saveAssociated.
*
* @param CakeEvent $event containing the Model
* @return void
*/
public function nestedSaveAssociated($event) {
$Model = $event->subject;
$Model->saveAssociated($Model->dataForAfterSave, array('callbacks' => false));
}
}

View file

@ -2096,7 +2096,7 @@ class CakeEmailTest extends CakeTestCase {
$result['html'] = false;
$length = count($message);
for ($i = 0; $i < $length; ++$i) {
if ($message[$i] == $boundary) {
if ($message[$i] === $boundary) {
$flag = false;
$type = '';
while (!preg_match('/^$/', $message[$i])) {

View file

@ -540,8 +540,8 @@ TEXT;
Debugger::dump($var, 1);
$result = ob_get_clean();
$open = php_sapi_name() == 'cli' ? "\n" : '<pre>';
$close = php_sapi_name() == 'cli' ? "\n" : '</pre>';
$open = php_sapi_name() === 'cli' ? "\n" : '<pre>';
$close = php_sapi_name() === 'cli' ? "\n" : '</pre>';
$expected = <<<TEXT
{$open}array(
'People' => array(

View file

@ -132,21 +132,21 @@ class String {
}
if ($tmpOffset !== -1) {
$buffer .= substr($data, $offset, ($tmpOffset - $offset));
if (!$depth && $data{$tmpOffset} == $separator) {
if (!$depth && $data{$tmpOffset} === $separator) {
$results[] = $buffer;
$buffer = '';
} else {
$buffer .= $data{$tmpOffset};
}
if ($leftBound != $rightBound) {
if ($data{$tmpOffset} == $leftBound) {
if ($leftBound !== $rightBound) {
if ($data{$tmpOffset} === $leftBound) {
$depth++;
}
if ($data{$tmpOffset} == $rightBound) {
if ($data{$tmpOffset} === $rightBound) {
$depth--;
}
} else {
if ($data{$tmpOffset} == $leftBound) {
if ($data{$tmpOffset} === $leftBound) {
if (!$open) {
$depth++;
$open = true;

View file

@ -109,6 +109,7 @@ class CacheHelper extends AppHelper {
* @param string $out output to cache
* @return string view output
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/cache.html
* @throws Exception If debug mode is enabled and writing to cache file fails.
*/
public function cache($file, $out) {
$cacheTime = 0;
@ -120,7 +121,7 @@ class CacheHelper extends AppHelper {
$index = null;
foreach ($keys as $action) {
if ($action == $this->request->params['action']) {
if ($action === $this->request->params['action']) {
$index = $action;
break;
}
@ -153,6 +154,10 @@ class CacheHelper extends AppHelper {
try {
$this->_writeFile($cached, $cacheTime, $useCallbacks);
} catch (Exception $e) {
if (Configure::read('debug')) {
throw $e;
}
$message = __d(
'cake_dev',
'Unable to write view cache file: "%s" for "%s"',

View file

@ -364,7 +364,7 @@ class TimeHelper extends AppHelper {
}
/**
* Formats date for RSS feeds
* Formats a date into a phrase expressing the relative time.
*
* ## Addition options
*