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](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. 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. 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 [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 [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 [#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! [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; list($type, $string, $line) = $countToken;
if (($type == T_STRING) && ($string == $functionName) && ($firstParenthesis === '(')) { if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) {
$position = $count; $position = $count;
$depth = 0; $depth = 0;

View file

@ -284,7 +284,7 @@ class CookieComponent extends Component {
return null; 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 Hash::get($this->_values[$this->name][$key], $names[1]);
} }
return $this->_values[$this->name][$key]; return $this->_values[$this->name][$key];

View file

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

View file

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

View file

@ -1316,6 +1316,7 @@ class DboSource extends DataSource {
foreach ($resultSet as &$row) { foreach ($resultSet as &$row) {
if ($type === 'hasOne' || $type === 'belongsTo' || $type === 'hasMany') { if ($type === 'hasOne' || $type === 'belongsTo' || $type === 'hasMany') {
$assocResultSet = array(); $assocResultSet = array();
$prefetched = false;
if ( if (
($type === 'hasOne' || $type === 'belongsTo') && ($type === 'hasOne' || $type === 'belongsTo') &&
@ -1326,6 +1327,7 @@ class DboSource extends DataSource {
if (!empty($joinedData)) { if (!empty($joinedData)) {
$assocResultSet[0] = array($LinkModel->alias => $row[$LinkModel->alias]); $assocResultSet[0] = array($LinkModel->alias => $row[$LinkModel->alias]);
} }
$prefetched = true;
} else { } else {
$query = $this->insertQueryData($queryTemplate, $row, $association, $Model, $stack); $query = $this->insertQueryData($queryTemplate, $row, $association, $Model, $stack);
if ($query !== false) { if ($query !== false) {
@ -1376,7 +1378,7 @@ class DboSource extends DataSource {
$this->_mergeAssociation($row, $assocResultSet, $association, $type, $selfJoin); $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); $row[$association] = $LinkModel->afterFind($row[$association], false);
} }
@ -1863,7 +1865,7 @@ class DboSource extends DataSource {
'type' => null, 'type' => null,
'alias' => null, 'alias' => null,
'table' => 'join_table', 'table' => 'join_table',
'conditions' => array() 'conditions' => '',
), $join); ), $join);
if (!empty($data['alias'])) { if (!empty($data['alias'])) {
@ -1917,7 +1919,7 @@ class DboSource extends DataSource {
* @return string * @return string
*/ */
public function renderJoinStatement($data) { 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 "{$data['type']} JOIN {$data['table']} {$data['alias']}";
} }
return trim("{$data['type']} JOIN {$data['table']} {$data['alias']} ON ({$data['conditions']})"); 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. * @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 * Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record saved successfully. * 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 * @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()) { public function saveMany($data = null, $options = array()) {
@ -2291,11 +2292,13 @@ class Model extends Object implements CakeEventListener {
$options['validate'] = false; $options['validate'] = false;
} }
$transactionBegun = false;
if ($options['atomic']) { if ($options['atomic']) {
$db = $this->getDataSource(); $db = $this->getDataSource();
$transactionBegun = $db->begin(); $transactionBegun = $db->begin();
} }
try {
$return = array(); $return = array();
foreach ($data as $key => $record) { foreach ($data as $key => $record) {
$validates = $this->create(null) !== null; $validates = $this->create(null) !== null;
@ -2333,8 +2336,16 @@ class Model extends Object implements CakeEventListener {
return true; return true;
} }
if ($transactionBegun) {
$db->rollback(); $db->rollback();
}
return false; return false;
} catch (Exception $e) {
if ($transactionBegun) {
$db->rollback();
}
throw $e;
}
} }
/** /**
@ -2386,6 +2397,7 @@ class Model extends Object implements CakeEventListener {
* @return mixed If atomic: True on success, or false on failure. * @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 * Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record saved successfully. * 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 * @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()) { public function saveAssociated($data = null, $options = array()) {
@ -2414,11 +2426,13 @@ class Model extends Object implements CakeEventListener {
$options['validate'] = false; $options['validate'] = false;
} }
$transactionBegun = false;
if ($options['atomic']) { if ($options['atomic']) {
$db = $this->getDataSource(); $db = $this->getDataSource();
$transactionBegun = $db->begin(); $transactionBegun = $db->begin();
} }
try {
$associations = $this->getAssociated(); $associations = $this->getAssociated();
$return = array(); $return = array();
$validates = true; $validates = true;
@ -2492,7 +2506,7 @@ class Model extends Object implements CakeEventListener {
if ($options['deep']) { if ($options['deep']) {
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options); $saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
} else { } else {
$saved = $Model->save($values, array('atomic' => false) + $options); $saved = $Model->save($values, $options);
} }
} }
@ -2542,8 +2556,16 @@ class Model extends Object implements CakeEventListener {
return true; return true;
} }
if ($transactionBegun) {
$db->rollback(); $db->rollback();
}
return false; return false;
} catch (Exception $e) {
if ($transactionBegun) {
$db->rollback();
}
throw $e;
}
} }
/** /**

View file

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

View file

@ -1197,6 +1197,11 @@ class DboSourceTest extends CakeTestCase {
'table' => 'posts_tags', 'table' => 'posts_tags',
'conditions' => array('1 = 1') 'conditions' => array('1 = 1')
), 'CROSS JOIN cakephp.posts_tags AS PostsTag'), ), '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( array(array(
'type' => 'LEFT', 'type' => 'LEFT',
'alias' => 'PostsTag', 'alias' => 'PostsTag',
@ -1456,4 +1461,51 @@ class DboSourceTest extends CakeTestCase {
$result = $db->defaultConditions($Article, null); $result = $db->defaultConditions($Article, null);
$this->assertFalse($result); $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; protected $_dataSourceObject;
public $dataForAfterSave;
/** /**
* Helper method to set a datasource object * Helper method to set a datasource object
* *
@ -74,6 +76,8 @@ class TestPost extends Post {
protected $_dataSourceObject; protected $_dataSourceObject;
public $dataForAfterSave;
/** /**
* Helper method to set a datasource object * Helper method to set a datasource object
* *
@ -4378,25 +4382,57 @@ class ModelWriteTest extends BaseModelTest {
public function testSaveAllManyRowsTransactionNoRollback() { public function testSaveAllManyRowsTransactionNoRollback() {
$this->loadFixtures('Post'); $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 = new TestPost();
$Post->setDataSourceObject($db);
$Post->validate = array( $Post->validate = array(
'title' => array('rule' => array('notEmpty')) '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( $data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'), array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => '') array('author_id' => 1, 'title' => '')
); );
$Post->saveAll($data, array('atomic' => true, 'validate' => true)); $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 * @return void
*/ */
public function testSaveAllAssociatedTransactionNoRollback() { public function testSaveAllAssociatedTransactionNoRollback() {
$testDb = ConnectionManager::getDataSource('test'); $this->loadFixtures('Post', 'Author');
$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')
)));
$Post = new TestPost(); $Post = new TestPost();
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$Post->Author->validate = array( $Post->Author->validate = array(
'user' => array('rule' => array('notEmpty')) '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( $data = array(
'Post' => array( 'Post' => array(
'title' => 'New post', 'title' => 'New post',
@ -4439,6 +4469,55 @@ class ModelWriteTest extends BaseModelTest {
) )
); );
$Post->saveAll($data, array('validate' => true)); $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() { public function testSaveManyTransactionNoRollback() {
$this->loadFixtures('Post'); $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 = new TestPost();
$Post->setDataSourceObject($db);
$Post->validate = array( $Post->validate = array(
'title' => array('rule' => array('notEmpty')) '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( $data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'), array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => '') array('author_id' => 1, 'title' => '')
); );
$Post->saveMany($data, array('validate' => true)); $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 * @return void
*/ */
public function testSaveAssociatedTransactionNoRollback() { public function testSaveAssociatedTransactionNoRollback() {
$testDb = ConnectionManager::getDataSource('test'); $this->loadFixtures('Post', 'Author');
$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')
)));
$Post = new TestPost(); $Post = new TestPost();
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$Post->Author->validate = array( $Post->Author->validate = array(
'user' => array('rule' => array('notEmpty')) '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( $data = array(
'Post' => array( 'Post' => array(
'title' => 'New post', 'title' => 'New post',
@ -5872,6 +5977,55 @@ class ModelWriteTest extends BaseModelTest {
) )
); );
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true)); $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 = $event->subject;
$Model->getDataSource()->delete($Model, array($Model->alias . '.' . $Model->primaryKey => $Model->id)); $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; $result['html'] = false;
$length = count($message); $length = count($message);
for ($i = 0; $i < $length; ++$i) { for ($i = 0; $i < $length; ++$i) {
if ($message[$i] == $boundary) { if ($message[$i] === $boundary) {
$flag = false; $flag = false;
$type = ''; $type = '';
while (!preg_match('/^$/', $message[$i])) { while (!preg_match('/^$/', $message[$i])) {

View file

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

View file

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

View file

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