Related
I want to populate a checkbox group with the array JournalEntry[strategy][strategies_conditions]
I then want to have the checkboxes selected that are contained in JournalEntry[journal_entries_strategy_conditions]
I have the checkboxes displaying but not linked correctly to the correct fields of name and id
This is the code I have in the edit.php
<?php
echo $this->Form->control('journal_id', ['options' => $journals]);
echo $this->Form->control('ticket_number');
echo $this->Form->control('strategy_id', ['options' => $strategies, 'empty' => true, 'onChange' => 'getComboA(this)']);
debug($journalEntry);
?>
<div id="stategy-condition">
<?php echo $this->Form->control('journal_entries_strategy_conditions.strategies_condition_id',
[
'type' => 'select',
'multiple' => 'checkbox',
'options' => $journalEntry[strategy][strategies_conditions],
'selected' => $journalEntry[journal_entries_strategy_conditions]
]); ?>
</div>
With this I get this output, as you can see it is using the object for the <label> and <input value="1" is set to the id value
<div class="input select">
<label for="strategy-id">Strategy</label>
<input type="hidden" name="strategy_id" value="">
<div class="checkbox">
<label for="strategy-id-0">
<input type="checkbox" name="strategy_id[]" value="0" id="strategy-id-0">{
"id": 1,
"strategy_id": 5,
"name": "zxcasd",
"level": "0",
"created": "2020-08-01T21:21:11+00:00",
"modified": "2020-08-01T21:21:11+00:00"
}</label>
</div>
<div class="checkbox">
<label for="strategy-id-1">
<input type="checkbox" name="strategy_id[]" value="1" id="strategy-id-1">{
"id": 2,
"strategy_id": 5,
"name": "zxcasd",
"level": "0",
"created": "2020-08-01T21:21:11+00:00",
"modified": "2020-08-01T21:21:11+00:00"
}</label>
</div>
<div class="checkbox">
<label for="strategy-id-2">
<input type="checkbox" name="strategy_id[]" value="2" id="strategy-id-2">{
"id": 3,
"strategy_id": 5,
"name": "zxcad",
"level": "0",
"created": "2020-08-01T21:21:11+00:00",
"modified": "2020-08-01T21:21:11+00:00"
}</label></div></div>
Here is the data:
object(App\Model\Entity\JournalEntry) {
'id' => (int) 20,
'journal_id' => (int) 1,
'ticket_number' => 'sdfsdfsdf',
'strategy_id' => (int) 5,
'timeframe' => '',
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-25 20:51:42.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-25 20:51:42.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'journal_entries_strategy_conditions' => [
(int) 0 => object(App\Model\Entity\JournalEntriesStrategyCondition) {
'id' => (int) 8,
'journal_entry_id' => (int) 20,
'strategies_condition_id' => (int) 1,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-25 20:51:42.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-25 20:51:42.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
}
],
'strategy' => object(App\Model\Entity\Strategy) {
'id' => (int) 5,
'user_id' => (int) 1,
'name' => 'zxcasd',
'description' => 'zxcasd',
'one_hundred_trades' => (int) 0,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'strategies_conditions' => [
(int) 0 => object(App\Model\Entity\StrategiesCondition) {
'id' => (int) 1,
'strategy_id' => (int) 5,
'name' => 'zxcasd',
'level' => '0',
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'[new]' => false,
'[accessible]' => [
'strategy_id' => true,
'name' => true,
'level' => true,
'created' => true,
'modified' => true,
'strategy' => true
]
},
(int) 1 => object(App\Model\Entity\StrategiesCondition) {
'id' => (int) 2,
'strategy_id' => (int) 5,
'name' => 'zxcasd',
'level' => '0',
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
},
(int) 2 => object(App\Model\Entity\StrategiesCondition) {
'id' => (int) 3,
'strategy_id' => (int) 5,
'name' => 'zxcad',
'level' => '0',
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-08-01 21:21:11.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
}
],
},
'[new]' => false,
This the request date for edit.php
[
'journal_id' => '1',
'ticket_number' => 'test con',
'pair' => '',
'buy_sell' => '',
'personal_notes' => '',
'entry_date_time' => '',
'entry_price' => '',
'strategy_id' => '5',
'journal_entries_strategy_conditions' => [
'strategies_condition_id' => [
(int) 0 => '1'
]
],
'timeframe' => '',
'position_size' => '',
'sl' => '',
'tp' => '',
'market_conditions' => '',
'entry_toughts' => '',
'close_date_time' => '',
'close_price' => '',
'profit' => '',
'fees' => '',
'high_price' => '',
'low_price' => '',
'exit_thoughts' => '',
'feeling_before' => '',
'feeling_after' => ''
]
add.php
[
'journal_id' => '1',
'ticket_number' => 'sdfdsf',
'pair' => '',
'buy_sell' => '',
'personal_notes' => '',
'entry_date_time' => '',
'entry_price' => '',
'strategy_id' => '5',
'journal_entries_strategy_conditions' => [
(int) 0 => [
'strategies_condition_id' => '1'
],
(int) 1 => [
'strategies_condition_id' => '2'
]
],
'timeframe' => '',
'position_size' => '',
'sl' => '',
'tp' => '',
'market_conditions' => '',
'entry_toughts' => '',
'close_date_time' => '',
'close_price' => '',
'profit' => '',
'fees' => '',
'high_price' => '',
'low_price' => '',
'exit_thoughts' => '',
'feeling_before' => '',
'feeling_after' => ''
]
There is no selected option, the option to provide values for selection is named val or value, and like the options option, it expects a flat key => value array (there's an exception if you want to provide attributes for <option> elements, in that case you can use nested arrays with text and value keys).
For options the array key will be use for the <option> element's value attribute, and the array value will be used for the content of the element.
For value the array value will be used for matching against the options array's keys, ie AFAICT you'd need an array of strategies_condition_id.
Often times values for select controls are being prepared using the list finder, you'll see that in baked controllers. However since you have nested associations, that's not necessarily feasible, and you're possibly better of converting the data after the fact, which can easily be done using collections.
For example in your view template:
<div id="stategy-condition">
<?php echo $this->Form->control('journal_entries_strategy_conditions', [
'type' => 'select',
'multiple' => 'checkbox',
'options' =>
collection($journalEntry['strategy']['strategies_conditions'])
->combine('id', 'name')
->toArray(),
'value' =>
collection($journalEntry['journal_entries_strategy_conditions'])
->extract('strategies_condition_id')
->toArray()
]); ?>
</div>
The combine() call should build a collection like:
[
1 => 'zxcasd',
2 => 'zxcasd',
3 => 'zxcad',
]
and the extract() should result in a collection containing this:
[
0 => 1
]
which should result in the first checkbox being checked.
Cookbook > Collections > combine()
Cookbook > Collections > extract()
As far as your control name problem goes, there are many ways to solve this, you could for example use a custom template where you hardcode the input name:
echo $this->Form->control('journal_entries_strategy_conditions', [
'type' => 'select',
// ...
'templates' => [
'checkbox' =>
'<input
type="checkbox"
name="journal_entries_strategy_conditions[][strategies_condition_id]"
value="{{value}}"
{{attrs}}>',
]
]);
Cookbook > Views > Helpers > Form > Options for Control
Cookbook > Views > Helpers > Form > Customizing the Templates FormHelper Uses
Or transform the data before marshalling (patching), using the beforeMarshal callback in your JournalEntriesTable class:
public function beforeMarshal(
\Cake\Event\EventInterface $event,
\ArrayObject $data,
\ArrayObject $options
) {
if (isset($data['journal_entries_strategy_conditions']['strategies_condition_id']) {
$ids = $data['journal_entries_strategy_conditions']['strategies_condition_id'];
$conditions = [];
foreach ($ids as $id) {
$conditions[] = [
'strategies_condition_id' => $id,
];
}
$data['journal_entries_strategy_conditions'] = $conditions;
}
}
This would transform the data from:
'journal_entries_strategy_conditions' => [
'strategies_condition_id' => [
'1',
'2'
],
],
to:
'journal_entries_strategy_conditions' => [
[
'strategies_condition_id' => '1'
],
[
'strategies_condition_id' => '2'
]
],
Cookbook > Database Access & ORM > Saving Data > Modifying Request Data Before Building Entities
Or even create the individual checkboxes manually, where you have proper control over every aspect of every form control, including the name:
echo $this->Form->label('journal_entries_strategy_conditions');
echo $this->Form->hidden('journal_entries_strategy_conditions', ['value' => '']);
$selectedIds = collection($journalEntry['journal_entries_strategy_conditions'])
->extract('strategies_condition_id')
->toArray();
foreach ($journalEntry['strategy']['strategies_conditions'] as $index => $condition) {
echo $this->Form->control(
"journal_entries_strategy_conditions.{$index}.strategies_condition_id",
[
'type' => 'checkbox',
'hiddenField' => false,
'label' => $condition['name'],
'value' => $condition['id'],
'checked' => in_array($condition['id'], $selectedIds, true),
]
);
}
This would create name attributes like:
journal_entries_strategy_conditions[0][strategies_condition_id]
journal_entries_strategy_conditions[1][strategies_condition_id]
...
Cookbook > Views > Helpers > Form > Creating Select, Checkbox and Radio Controls
I want to flatten a tree structure with CakePHP's listNested function as explained here: http://book.cakephp.org/3.0/en/core-libraries/collections.html#Cake\Collection\Collection::listNested
I've tried the given example, but I don't get the expected result as in the example. What am I doing wrong or is this a bug in the listNested function?
My code:
$data = [
[
'id' => 1,
'parent_id' => null,
'name' => 'Birds',
'children' => [
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []],
]
],
[
'id' => 6,
'parent_id' => null,
'name' => 'Fish',
'children' => [
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []],
]
]
];
$collection = new Collection($data);
$flatList = $collection->listNested();
debug($flatList->toArray());
My result:
[
(int) 0 => [
'id' => (int) 5,
'parent_id' => (int) 6,
'name' => 'Clown Fish',
'children' => []
],
(int) 1 => [
'id' => (int) 6,
'parent_id' => null,
'name' => 'Fish',
'children' => [
(int) 0 => [
'id' => (int) 5,
'parent_id' => (int) 6,
'name' => 'Clown Fish',
'children' => []
]
]
],
(int) 2 => [
'id' => (int) 4,
'parent_id' => (int) 1,
'name' => 'Seagull',
'children' => []
]
]
Expected result:
[
['id' => 1, 'parent_id' => null, 'name' => 'Birds'],
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
['id' => 6, 'parent_id' => null, 'name' => 'Fish'],
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish']
]
I have a deep associated find and one association is retrieving too many none related records for modules_employees.
I should see only one record for modules_employees under the course_modules but it retrieves many because they can be many with course_modules_id but only one with courses_employee_id.
The modules_employees table
'id' => (int) 18,
'courses_employee_id' => (int) 31,
'course_module_id' => (int) 7,
'completed_on' => null,
CoursesEmployee->course->course_modules->modules_employees
CoursesEmployeesController.php
public function player($id = null)
{
$coursesEmployee = $this->CoursesEmployees->get($id, [
'contain' =>
[
'Employees',
'Courses',
'CourseModules',
'Courses.CourseModules',
'Courses.CourseModules.ModulesEmployees',
'Courses.CourseFiles'
]
]);
$this->set('coursesEmployee', $coursesEmployee);
debug($coursesEmployee);
$this->set('_serialize', ['coursesEmployee']);
}
The current find object, you will see one of the course_modules has two modules_employees when I should have one.
object(App\Model\Entity\CoursesEmployee) {
'id' => (int) 31,
'employee_id' => (int) 3,
'course_id' => (int) 3,
'course_module_id' => (int) 7,
'course_module' => object(App\Model\Entity\CourseModule) {
'id' => (int) 7,
'course_id' => (int) 3,
'name' => 'Module 2',
},
'course' => object(App\Model\Entity\Course) {
'id' => (int) 3,
'name' => 'Treacys Hotel Induction Training',
'course_files' => [
(int) 0 => object(App\Model\Entity\CourseFile) {
'id' => (int) 2,
'name' => 'Manual_Handling_doc.txt',
'type' => 'doc',
}
],
'course_modules' => [
(int) 0 => object(App\Model\Entity\CourseModule) {
'id' => (int) 6,
'course_id' => (int) 3,
'name' => 'Module 1',
'module_order' => (int) 1,
'modules_employees' => [
(int) 0 => object(App\Model\Entity\ModulesEmployee) {
'id' => (int) 1,
'courses_employee_id' => (int) 0,
'course_module_id' => (int) 6,
'started_on' => object(Cake\I18n\Time) {
'time' => '2015-09-08T04:16:16+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'completed_on' => object(Cake\I18n\Time) {
'time' => '2015-09-09T08:22:16+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'completed' => true,
'deleted' => null,
'[new]' => false,
'[accessible]' => [
'employee_id' => true,
'module_id' => true,
'started_on' => true,
'completed_on' => true,
'completed' => true,
'employee' => true,
'module' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'ModulesEmployees'
}
],
'[repository]' => 'CourseModules'
},
(int) 1 => object(App\Model\Entity\CourseModule) {
'id' => (int) 7,
'course_id' => (int) 3,
'name' => 'Module 2',
'module_order' => (int) 2,
'modules_employees' => [
(int) 0 => object(App\Model\Entity\ModulesEmployee) {
'id' => (int) 2,
'courses_employee_id' => (int) 31,
'course_module_id' => (int) 7,
'started_on' => object(Cake\I18n\Time) {
'time' => '2015-09-17T00:00:00+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'completed_on' => null,
'[repository]' => 'ModulesEmployees'
},
(int) 1 => object(App\Model\Entity\ModulesEmployee) {
'id' => (int) 18,
'courses_employee_id' => (int) 32,
'course_module_id' => (int) 7,
'started_on' => object(Cake\I18n\Time) {
'time' => '2015-09-17T00:00:00+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'completed_on' => null,
'[repository]' => 'ModulesEmployees'
}
],
'[repository]' => 'CourseModules'
},
],
},
'employee' => object(App\Model\Entity\Employee) {
'id' => (int) 3,
'user_id' => (int) 4,
},
'[repository]' => 'CoursesEmployees'
}
You should look into matching
http://book.cakephp.org/3.0/en/orm/query-builder.html#filtering-by-associated-data
$query = $this->CoursesEmployees->findById($id)
->contain(['Your_Models_You_Wanna_Contain'])
->matching('Courses.CourseModules.ModulesEmployees', function ($q) use ($id) {
return $q->where(['courses_employee_id' => $id]);
});
if nothing matches then you wont get a CourseEmployee back aswell, if you would still need that you could also use contain:
http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html#passing-conditions-to-contain
$query = $this->CoursesEmployees->findById($id)->contain([
'Courses.CourseModules.ModulesEmployees' => function ($q) use ($id) {
return $q
->where(['courses_employee_id' => $id]);
}
]);
I have a problem with CakePHP. I tested many ways, but can't find a solution for it.
I have these tables:
users
id
name
country_code
countries
code
title
skills
code
title
roles
id
title
events
id
title
country_code
events_users
event_id
user_id
role_id
skill_code
events hasAndBelongsToMany users and events_users is my join table, and when I read data, it gives me something like this:
array(
(int) 0 => array(
'Event' => array(
'id' => '1',
'title' => '40th WorldSkills Competitions',
'year' => '2011',
'country_code' => 'LV',
'created' => '2013-02-08 00:00:00'
),
'User' => array(
(int) 0 => array(
'password' => '*****',
'id' => '14',
'name' => 'test',
'family' => 'testian',
'username' => 'test#mail.me',
'country_code' => 'BR',
'telphone' => '465465',
'avatar' => 'avatar_51299c1da268f20110912043238_rodekamp_04.jpg',
'address' => 'AA',
'admin' => '0',
'created' => '2013-02-24 05:50:37',
'modified' => '2013-02-24 05:50:37',
'EventsUser' => array(
'id' => '1',
'user_id' => '14',
'event_id' => '1',
'role_id' => '1',
'skill_code' => '17',
'manage' => '100'
)
),
(int) 1 => array(
'password' => '*****',
'id' => '5',
'name' => 'John',
'family' => 'Smith',
'username' => 'john#me.com',
'country_code' => 'IE',
'telphone' => '147',
'avatar' => '20120504124040_franziska_peter_ok.jpg',
'address' => 'No 55',
'admin' => '0',
'created' => '2013-02-10 15:24:13',
'modified' => '2013-02-10 15:28:50',
'EventsUser' => array(
'id' => '2',
'user_id' => '5',
'event_id' => '1',
'role_id' => '2',
'skill_code' => '17',
'manage' => '0'
)
),
(int) 2 => array(
'password' => '*****',
'id' => '4',
'name' => 'hadi',
'family' => 'mm',
'username' => 'design.zerzem#gmail.com',
'country_code' => 'AE',
'telphone' => '415456',
'avatar' => '',
'address' => 'sadjfklsaf asd f',
'admin' => '0',
'created' => '2013-02-10 09:01:28',
'modified' => '2013-02-24 06:43:42',
'EventsUser' => array(
'id' => '3',
'user_id' => '4',
'event_id' => '1',
'role_id' => '4',
'skill_code' => '17',
'manage' => '0'
)
)
)
It's ok, but with events_users I want get data about skill and role that are in events_users (skill_code and role_id). How should I create models to give me all this data?
I think reading about "hasMany Through" should answer your question.
Basically, you make a model for your join table, then you can associate it like any other model.
When you go to retrieve your data, use CakePHP's Containable behavior (if you haven't used it before, be prepared to get addicted to it).
I'm new to programming and testing. I'm stuck at this testing, which throws an exception.
Please help me as I really don't get how I should approach this question.
My method to test:
public function saveNewInstitution($institutionData, $user) {
$institutionData[$this->alias]['isActive'] = 1;
$institutionData[$this->alias]['users_id'] = $user['User']['id'];
$this->create();
$this->set($institutionData);
if ($this->validates()) {
return $this->save();
} else {
throw new ValidationException('Not all fields are filled out correct');
}
}
My testclass:
public function testSaveNewInstitution() {
$result = array(
'Institution' => array(
'id' => 2,
'name' => 'Spitex',
'address' => 'Hauptweg 4',
'postcode' => '1234',
'city' => 'huuh',
'phone_number' => '123 456 78 90',
'email' => 'staufen#tsdy.huuh',
'comment' => '',
'isActive' => TRUE,
'users_id' => 2,
'institution_types_id' => 5
),
'Users' => array(
'id' => 2,
'email' => 'herbert#xyz.ch',
'password' => AuthComponent::password('12345678'),
'isMale' => TRUE,
'first_name' => 'Herbert',
'last_name' => 'Müller',
'address' => 'Hauptstrasse 1',
'postcode' => '1234',
'city' => 'Zürich',
'phone_number' => '123 456 78 90',
'isActive' => FALSE,
'institutions_id' => 2,
'groups_id' => 4
),
'InstitutionTypes' => array(
'id' => 5,
'name' => 'Spitex'
),
'Assignee' => array('0' => array(
'id' => 2,
'email' => 'herbert#xyz.ch',
'password' => AuthComponent::password('12345678'),
'isMale' => TRUE,
'first_name' => 'Herbert',
'last_name' => 'Müller',
'address' => 'Hauptstrasse 1',
'postcode' => '1234',
'city' => 'Zürich',
'phone_number' => '123 456 78 90',
'isActive' => FALSE,
'institutions_id' => 2,
'groups_id' => 4
))
);
$expected = $this->Institution->saveNewInstitution($result, 2);
$this->assertEqual($result, $expected);
}
public function testSaveNewInstitutionException() {
$this->setExpectedException('ValidationException');
$expected = array(
'Institution' => array(
'id' => 2,
'name' => 'Spitex',
'address' => 'Hauptweg 4',
'postcode' => '1234',
'city' => 'huuh',
'phone_number' => '123 456 78 90',
'email' => 'staufen#xyz.huuh',
'comment' => '',
'isActive' => TRUE,
'users_id' => 2,
'institution_types_id' => 5
),
'Users' => array(
'id' => 2,
'email' => 'herbert#xyz.ch',
'password' => AuthComponent::password('12345678'),
'isMale' => TRUE,
'first_name' => 'Herbert',
'last_name' => 'Müller',
'address' => 'Hauptstrasse 1',
'postcode' => '1234',
'city' => 'Zürich',
'phone_number' => '123 456 78 90',
'isActive' => FALSE,
'institutions_id' => 2,
'groups_id' => 4
),
'InstitutionTypes' => array(
'id' => 5,
'name' => 'Spitex'
),
'Assignee' => array('0' => array(
'id' => 2,
'email' => 'herbert#mueller.ch',
'password' => AuthComponent::password('12345678'),
'isMale' => TRUE,
'first_name' => 'Herbert',
'last_name' => 'Müller',
'address' => 'Hauptstrasse 1',
'postcode' => '1234',
'city' => 'Zürich',
'phone_number' => '123 456 78 90',
'isActive' => FALSE,
'institutions_id' => 2,
'groups_id' => 4
))
);
$this->Institution->saveNewInstitution($expected, 2);
}
My exception:
**VALIDATIONEXCEPTION**
Not all fields are filled out correct
Test case: InstitutionTest(testSaveNewInstitution)
Stack trace:
/app/Test/Case/Model/InstitutionTest.php : 148
InstitutionTest::testSaveNewInstitution
/usr/lib/php/PHPUnit/Framework/TestCase.php : 969
/usr/lib/php/PHPUnit/Framework/TestCase.php : 824
/usr/lib/php/PHPUnit/Framework/TestResult.php : 648
/usr/lib/php/PHPUnit/Framework/TestCase.php : 769
/lib/Cake/TestSuite/CakeTestCase.php : 78
/usr/lib/php/PHPUnit/Framework/TestSuite.php : 775
/usr/lib/php/PHPUnit/Framework/TestSuite.php : 745
/usr/lib/php/PHPUnit/TextUI/TestRunner.php : 346
/lib/Cake/TestSuite/CakeTestRunner.php : 57
/lib/Cake/TestSuite/CakeTestSuiteCommand.php : 111
/lib/Cake/TestSuite/CakeTestSuiteDispatcher.php : 242
/lib/Cake/TestSuite/CakeTestSuiteDispatcher.php : 99
/lib/Cake/TestSuite/CakeTestSuiteDispatcher.php : 116
/app/webroot/test.php : 92
9/9 test methods complete: 8 passes, 0 fails, 12 assertions and 1 exceptions.
as stated in the comments, the issue is pretty straigt forward. your validation fails and therefore the exception is thrown (as desired by your code).
if you want to debug it, try to determine what
$this->validationErrors
contains after the save() (before/instead of throwing the exception). then you know why the validation fails.