I'm trying to display names instead of ids on one of my tables.
I have a WorkRequest which has many WorkRequestChecks of each Checkbox.
These are my table create statements
CREATE TABLE `work_requests` (
`id` int(5) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
CREATE TABLE `work_request_checks` (
`work_request_id` int(11) NOT NULL,
`checkbox_id` int(11) NOT NULL,
`value` int(3) DEFAULT NULL,
PRIMARY KEY (`work_request_id`,`checkbox_id`)
);
CREATE TABLE `checkboxes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
);
From the WorkRequest page I am trying to show a list of WorkRequestChecks that have been entered. This is working fine apart from the fact it seems like cakephp isn't seeing the WorkRequestCheck as an object. It will display the ids of the Checkbox but when I try reference the ->name I get the error.
include - APP/Template/WorkRequests/view.ctp, line 193
Cake\View\View::_evaluate() - CORE/src/View/View.php, line 1010
Cake\View\View::_render() - CORE/src/View/View.php, line 971
Cake\View\View::render() - CORE/src/View/View.php, line 595
Cake\Controller\Controller::render() - CORE/src/Controller/Controller.php, line 623
Cake\Http\ActionDispatcher::_invoke() - CORE/src/Http/ActionDispatcher.php, line 125
Cake\Http\ActionDispatcher::dispatch() - CORE/src/Http/ActionDispatcher.php, line 93
Cake\Http\BaseApplication::__invoke() - CORE/src/Http/BaseApplication.php, line 78
Cake\Http\Runner::__invoke() - CORE/src/Http/Runner.php, line 65
Cake\Routing\Middleware\RoutingMiddleware::__invoke() - CORE/src/Routing/Middleware/RoutingMiddleware.php, line 59
Cake\Http\Runner::__invoke() - CORE/src/Http/Runner.php, line 65
Cake\Routing\Middleware\AssetMiddleware::__invoke() - CORE/src/Routing/Middleware/AssetMiddleware.php, line 88
Cake\Http\Runner::__invoke() - CORE/src/Http/Runner.php, line 65
Cake\Error\Middleware\ErrorHandlerMiddleware::__invoke() - CORE/src/Error/Middleware/ErrorHandlerMiddleware.php, line 93
Cake\Http\Runner::__invoke() - CORE/src/Http/Runner.php, line 65
DebugKit\Middleware\DebugKitMiddleware::__invoke() - ROOT/vendor/cakephp/debug_kit/src/Middleware/DebugKitMiddleware.php, line 52
Cake\Http\Runner::__invoke() - CORE/src/Http/Runner.php, line 65
This is strange as I have this working on WorkRequestChecks index page as displayed below.
This a photo of the errors I get referencing Checkbox name on the WorkRequests page.
This is my code for my WorkRequests view.ctp relating the WorkRequestChecks
<?php foreach ($workRequest->work_request_checks as $Checks): ?>
<tr>
<td><?= $this->Html->link($Checks->checkbox->name, ['controller' => 'Checkboxes', 'action' => 'view', $Checks->checkbox->id])?></td>
<td><?= h($Checks->value) ?></td>
</tr>
<?php endforeach; ?>
I tried printing the $workRequest object and this is the result
object(App\Model\Entity\WorkRequest) {
'id' => (int) 80590,
'invoice_id' => (int) 21412009,
'item' => '1',
'defect' => 'Calibration Due',
'model_no' => 'TE135 30dB Attenuator',
'part_no' => 'SA4N50-30',
'quantity' => '1',
'serial_no' => 'NSN',
'status_id' => '3',
'worksheet_id' => 'WSCC',
'remarks' => '',
'mm' => '',
'cal_cert_ref' => '0',
'test_equipment_id' => '',
'ATA_chapter' => '',
'pub' => '0',
'pub_last_controlled' => '',
'test_harness_used' => '',
'closed' => (int) 1,
'work_request_checks' => [
(int) 0 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 1,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 1 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 2,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 2 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 3,
'value' => (int) 0,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 3 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 4,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 4 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 5,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 5 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 6,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 6 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 8,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 7 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 17,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
},
(int) 8 => object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 19,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
}
],
'work_tasks' => [],
'test_equipment' => null,
'worksheet' => null,
'status' => object(App\Model\Entity\Status) {
'id' => (int) 3,
'name' => 'Calibrated',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Statuses'
},
'invoice' => object(App\Model\Entity\Invoice) {
'id' => (int) 21412009,
'start_date' => object(Cake\I18n\FrozenDate) {
'time' => '2014-12-01T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'close_date' => object(Cake\I18n\FrozenDate) {
'time' => '2014-12-01T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'invoice_to_id' => '1504',
'contact_name' => '',
'phone_number' => '',
'aircraft_reg_id' => '',
'ship_to_address' => '',
'ship_via_id' => '89',
'notes' => 'Item 1: TE135 30DB ATTENUATOR P/N: SA4N50-30 S/N: NSN. Defect Reported: Calibration Due., Calibration carried out and found serviceable.',
'worksheet_notes' => 'Item 1: TE135 30DB ATTENUATOR P/N: SA4N50-30 S/N: NSN. Defect Reported: Calibration Due., Calibration carried out and found serviceable.',
'closed' => true,
'times_printed' => '1',
'payment_due' => '1',
'opening_notes' => '',
'activity_counter' => '0',
'last_viewed' => object(Cake\I18n\FrozenTime) {
'time' => '2014-12-15T08:24:00+13:45',
'timezone' => 'NZ-CHAT',
'fixedNowTime' => false
},
'last_viewed_by_id' => '62',
'courier_ticket_no' => '',
'job_description' => 'Calibration',
'spell_checked' => true,
'authorisation_no' => '',
'priority_id' => '',
'job_type_id' => '',
'opened_by_id' => '47',
'assigned_to_id' => '47',
'checked_out_to_id' => (int) 1,
'exclued_from_unclosed_job_report' => false,
'certification_required' => false,
'requested_finish_date' => object(Cake\I18n\FrozenDate) {
'time' => '2014-12-04T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'exclude_from_requested_finish_date' => false,
'supervising_engineer_id' => '1',
'quote_ref' => '1',
'currency_id' => '1',
'ent_MYOB' => true,
'MYOB_amount' => '$113.85',
'MYOB_batch' => '664',
'exchange_rate' => '1',
'payment_instructions_id' => '1',
'invoice_emailed' => true,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Invoices'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequests'
}
Then I tried printing on of the $Checks
object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 1,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
}
This is my WorkRequest controller view method
public function view($id = null)
{
$workRequest = $this->WorkRequests->get($id, [
'contain' => ['WorkRequestChecks']
]);
$this->set('workRequest', $workRequest);
$this->set('_serialize', ['workRequest']);
Here's part of my WorkRequestsTable.php file
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('work_requests');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->hasMany('WorkRequestChecks', [
'foreignKey' => 'work_request_id'
]);
}
Here's part of my CheckboxesTable.php file
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('checkboxes');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->belongsTo('CheckboxTypes', [
'foreignKey' => 'checkbox_type_id',
'joinType' => 'INNER'
]);
}
Here's part of my WorkRequestChecksTable.php file
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('work_request_checks');
$this->setDisplayField('work_request_id');
$this->setPrimaryKey(['work_request_id', 'checkbox_id']);
$this->belongsTo('WorkRequests', [
'foreignKey' => 'work_request_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Checkboxes', [
'foreignKey' => 'checkbox_id',
'joinType' => 'INNER'
]);
}
I'm not sure why this isn't working; it seems like it's converted the object $Check to an array.
As in your CTP, you're trying to access $Checks->checkbox->name
But as your dump says, it contains just:
object(App\Model\Entity\WorkRequestCheck) {
'work_request_id' => (int) 80590,
'checkbox_id' => (int) 1,
'value' => (int) 1,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'WorkRequestChecks'
}
So, there is no checkbox field at all and then will always be null and you'll get the error.
You need to associate it in your model if you want to retrieve data from a checkbox with id. Also check the recursive, you might want to change it to 1 or 2.
Related
I've got a products, attributes, and product_attributes table.
Each product can have multiple attributes and these relations were stored in product_attributes, so far so good.
But a product can have the same attribute multiple times. I just only can link different attributes to a product.
For example:
Audi(product) has Wheel(attribute): Amount(joinData) = 2;
Value(joinData) = "front";
And
Audi(product) has Wheel(attribute): Amount(joinData) = 2;
Value(joinData) = "rear";
Only the attribute wheel with value rear is saved the front wheels are lost.
No errors in controller.
This is the output of $this->request->getData()
/src/Controller/ProductsController.php (line 62)
[
'type_id' => '12',
'name' => 'Audi',
'thumbnail' => '',
'image' => '',
'attributes' => [
(int) 0 => [
'unique_id' => '9',
'_joinData' => [
'amount' => '2',
'value' => '1',
'information' => 'front'
]
],
(int) 1 => [
'unique_id' => '9',
'_joinData' => [
'amount' => '2',
'value' => '1',
'information' => 'rear'
]
]
]
]
This is the output of $product after patchEntity:
object(App\Model\Entity\Product) {
'type_id' => (int) 12,
'name' => 'Audi',
'thumbnail' => '',
'image' => '',
'attributes' => [
(int) 0 => object(App\Model\Entity\Attribute) {
'unique_id' => (int) 9,
'category_id' => (int) 8,
'name' => 'VDSL2',
'unit' => '',
'_joinData' => object(App\Model\Entity\ProductsAttribute) {
'amount' => (int) 2,
'value' => (float) 1,
'information' => 'rear',
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'amount' => true,
'value' => true,
'information' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'ProductsAttributes'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => [
'amount' => '2',
'value' => '0',
'information' => 'front'
]
],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Attributes'
},
(int) 1 => object(App\Model\Entity\Attribute) {
'unique_id' => (int) 9,
'category_id' => (int) 8,
'name' => 'VDSL2',
'unit' => '',
'_joinData' => object(App\Model\Entity\ProductsAttribute) {
'amount' => (int) 2,
'value' => (float) 1,
'information' => 'rear',
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'amount' => true,
'value' => true,
'information' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'ProductsAttributes'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => [
'amount' => '2',
'value' => '0',
'information' => 'front'
]
],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Attributes'
}
],
'[new]' => true,
'[accessible]' => [
'type_id' => true,
'name' => true,
'thumbnail' => true,
'image' => true,
'type' => true,
'attributes' => true
],
'[dirty]' => [
'type_id' => true,
'name' => true,
'thumbnail' => true,
'image' => true,
'attributes' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Products'
}
ProductController Add function:
public function add(){
$product = $this->Products->newEntity();
if ($this->request->is('post')) {
$product = $this->Products->patchEntity($product, $this->request->getData(), [
'associated' => [
'Attributes',
'Attributes._joinData'
]
]);
/*debug($this->request->getData());
debug($product);
die();*/
if ($this->Products->save($product)) {
$this->Flash->success(__('The product has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The product could not be saved. Please, try again.'));
}
$types = $this->Products->Types->find('list', ['limit' => 200]);
$attributes = $this->Products->Attributes->find('list', ['limit' => 200]);
$this->set(compact('product', 'types', 'attributes'));
}
It has something to do with my relations.
Product Table:
$this->belongsToMany('Attributes', [
'foreignKey' => 'product_id',
'targetForeignKey' => 'attribute_id',
'joinTable' => 'products_attributes'
]);
Attribute Table:
$this->belongsToMany('Products', [
'foreignKey' => 'attribute_id',
'targetForeignKey' => 'product_id',
'joinTable' => 'products_attributes'
]);
These are the attributes product_attributes have in the DB:
unique_iq INT,
product_id INT,
attribute_id INT,
amount INT,
value DOUBLE,
information VARCHAR(150)
Save Strategy doesn't solve my problem. Still same result.
Just a workaround but it should work. Waiting for a more cake way
Since you basically want to fill products and product_attributes tables you can set a new relationship this way
Product Table:
$this->hasMany('ProductsAttributes', [ /* configure keys here */ ]);
And shape you data this way
[
'type_id' => '12',
'name' => 'Audi',
'thumbnail' => '',
'image' => '',
'products_attributes' => [
[
'attribute_id' => '9',
'amount' => '2',
'value' => '1',
'information' => 'front'
],
[
'attribute_id' => '9',
'amount' => '2',
'value' => '1',
'information' => 'rear'
]
]
]
This will create a new row in products and two new rows in product_attributes
When I query without select() I get contain() [owner] array, but using select() with params it doesn't contain [owner] array. Is there any problem using select and contain at same time ?
$payments = $this->Payments->find()
->select($this->Payments)
->select(['tot_amount'=>'sum(Payments.amount)'])
->select(['tot_owner_amt'=>'sum(Payments.owner_amt)'])
->select(['tot_admin_amt'=>'sum(Payments.admin_amt)'])
->contain(['Owners'])
->group(['Payments.owner_id'])
->all();
debug($payments); die();
Debug array
\src\Controller\PaymentsController.php (line 72)
object(Cake\ORM\ResultSet) {
'items' => [
(int) 0 => object(App\Model\Entity\Payment) {
'id' => (int) 4,
'user_id' => (int) 4,
'owner_id' => (int) 5,
'wifi_id' => (int) 2,
'payment_unique_key' => 'pay-9999',
'payment_type' => 'paypal',
'amount' => (float) 10,
'owner_amt' => (float) 1,
'admin_amt' => (float) 9,
'ispaid' => false,
'paid_at' => object(Cake\I18n\FrozenTime) {
'time' => '2017-01-10T11:21:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'is_refund' => false,
'isactive' => true,
'isdel' => null,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2017-01-10T11:22:24+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2017-01-10T11:30:26+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'tot_amount' => '10.00',
'tot_owner_amt' => '1.00',
'tot_admin_amt' => '9.00',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Payments'
},
(int) 1 => object(App\Model\Entity\Payment) {
'id' => (int) 1,
'user_id' => (int) 1,
'owner_id' => (int) 6,
'wifi_id' => (int) 1,
'payment_unique_key' => 'adfasdf',
'payment_type' => 'paypal',
'amount' => (float) 500,
'owner_amt' => (float) 50,
'admin_amt' => (float) 449,
'ispaid' => false,
'paid_at' => object(Cake\I18n\FrozenTime) {
'time' => '2017-01-10T11:21:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'is_refund' => false,
'isactive' => true,
'isdel' => null,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2017-01-10T11:22:24+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2017-01-10T11:30:26+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'tot_amount' => '2000.00',
'tot_owner_amt' => '200.00',
'tot_admin_amt' => '1798.00',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Payments'
}
]
}
Did i miss anything in above code? Thanks in advance
If you don't select the owner data it doesn't fetch the owner? Pretty simple as that. You select only the payments, add the owner like you do with the payments.
Need to select the owners array in select like select($this->Payments->Owners) , Thanks #burzum
Solutions
$payments = $this->Payments->find()
->select($this->Payments)
->select(['tot_amount'=>'sum(Payments.amount)'])
->select(['tot_owner_amt'=>'sum(Payments.owner_amt)'])
->select(['tot_admin_amt'=>'sum(Payments.admin_amt)'])
->select($this->Payments->Owners)
->contain(['Owners'])
->group(['Payments.owner_id'])
->all();
In the parlance of the classic BelongsToMany example, I'm trying to create a new Student with associated Courses records, and final_grade data for each of the Student's Courses. (The Courses records themselves already exist.) When I attempt to create the Student, I'm sending courses[0][_joinData][course_id] and courses[0][_joinData][final_grade], but when it tries to save the new CoursesStudents record, the validation complains that it doesn't have a student_id field. I'm not able to pass it in, because it doesn't exist yet!
I can associate the new Student records with existing Courses by sending data as courses[_ids], and that correctly picks up the newly provisioned Course's id for the course_id for the join table's new record. But I haven't been able to figure out a way to include non-default join data.
(My real models are AccessGroups and Markets, but the relationships are the same. I want to be able to associate a new AccessGroup with an existing Market at a specific access_level at the time of creation, without having to generate the AccessGroupsMarkets record after the fact.)
Edit
Code:
I'm adding my code and the responses I'm getting back from CakePHP, as suggested by #burzum:
// in a controller
$accessGroupsTable = TableRegistry::get('AccessGroups');
$data = [
'name' => 'Test for Access Group with Market Join Data',
'access_group_type_id' => '1',
'project_id' => '49',
'markets' => [
0 => [
'id' => '46',
'_joinData' => [
'market_id' => '46',
'access_level' => '2',
]
],
1 => [
'id' => '47',
'_joinData' => [
'market_id' => '47',
'access_level' => '2',
]
]
],
];
$newAccessGroup = $accessGroupsTable->newEntity($data);
debug(["newAccessGroup", $newAccessGroup], false); // debug!
$saved = $accessGroupsTable->save($newAccessGroup);
debug(["saved", $saved], false); // debug!
Response:
/src/Controller/AccessGroupsController.php (line 161)
########## DEBUG ##########
[
(int) 0 => 'newAccessGroup',
(int) 1 => object(App\Model\Entity\AccessGroup) {
'name' => 'Test for Access Group with Market Join Data',
'access_group_type_id' => (int) 1,
'project_id' => (int) 49,
'markets' => [
(int) 0 => object(App\Model\Entity\Market) {
'id' => (int) 46,
'project_id' => (int) 49,
'name' => 'United States - Flammmo',
'created' => object(Cake\I18n\Time) {
'time' => '2016-05-13T18:42:11+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2016-05-13T18:42:11+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'deleted' => null,
'_joinData' => object(App\Model\Entity\AccessGroupsMarket) {
'market_id' => (int) 46,
'access_level' => (int) 2,
'[new]' => true,
'[accessible]' => [
'market_id' => true,
'access_group_id' => true,
'access_level' => true
],
'[dirty]' => [
'market_id' => true,
'access_level' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [
'access_group_id' => [
'_required' => 'This field is required'
]
],
'[invalid]' => [],
'[repository]' => 'AccessGroupsMarkets'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => [
'market_id' => '46',
'access_level' => '2'
]
],
'[virtual]' => [
(int) 0 => 'flag_url'
],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Markets'
},
(int) 1 => object(App\Model\Entity\Market) {
'id' => (int) 47,
'project_id' => (int) 49,
'name' => 'Indonesia - Flammmo',
'created' => object(Cake\I18n\Time) {
'time' => '2016-06-24T16:24:57+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2016-06-24T16:24:57+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'deleted' => null,
'_joinData' => object(App\Model\Entity\AccessGroupsMarket) {
'market_id' => (int) 47,
'access_level' => (int) 2,
'[new]' => true,
'[accessible]' => [
'market_id' => true,
'access_group_id' => true,
'access_level' => true
],
'[dirty]' => [
'market_id' => true,
'access_level' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [
'access_group_id' => [
'_required' => 'This field is required'
]
],
'[invalid]' => [],
'[repository]' => 'AccessGroupsMarkets'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => [
'market_id' => '47',
'access_level' => '2'
]
],
'[virtual]' => [
(int) 0 => 'flag_url'
],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Markets'
}
],
'[new]' => true,
'[accessible]' => [
'project_id' => true,
'access_group_type_id' => true,
'name' => true,
'slug' => true,
'features' => true,
'assets' => true,
'markets' => true,
'child_groups' => true,
'users' => true
],
'[dirty]' => [
'name' => true,
'access_group_type_id' => true,
'project_id' => true,
'markets' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'AccessGroups'
}
]
###########################
/src/Controller/AccessGroupsController.php (line 177)
########## DEBUG ##########
[
(int) 0 => 'saved',
(int) 1 => false
]
###########################
Note the validation errors on both of the markets' _joinData access_group_id
Working code that's not quite what I want
$accessGroupsTable = TableRegistry::get('AccessGroups');
$data2 = [
'name' => 'Test for Access Group with Market IDs',
'access_group_type_id' => '1',
'project_id' => '49',
'markets' => [
'_ids' => [ 46, 47 ],
],
];
$newAccessGroup = $accessGroupsTable->newEntity($data2);
$saved = $accessGroupsTable->save($newAccessGroup);
This code successfully creates the new AccessGroup, along with the associated new AccessGroupsMarkets records, but with the default values for access_level, not the value that I'd like to be able to specify.
#systematical asked about the table setup. They were generated from a migration and then cake/bake'd (see the Table associations)
The problem (pointed out by #npm on IRC #cakephp) was that I was doing validation on the foreign keys in my join table's model:
// in AccessGroupsMarketsTable::validationDefault
$validator
->requirePresence('access_group_id', 'create')
->notEmpty('access_group_id')
->add('access_group_id', 'valid', ['rule' => 'numeric']);
$validator
->requirePresence('market_id', 'create')
->notEmpty('market_id')
->add('market_id', 'valid', ['rule' => 'numeric']);
This was in addition to the $rules in buildRules:
$rules->add($rules->existsIn(['access_group_id'], 'AccessGroups'));
$rules->add($rules->existsIn(['market_id'], 'Markets'));
#npm suggested that I remove the validation for those foreign keys (keeping the $rules in place). And his suggestion works. I can now save the new AccessGroups entity with its related AccessGroupsMarkets records and their associated access_level information.
So I noticed that If I patch an entity (edit method) and whether or not I make any data changes to the record if it has belongsToMany associations it marks them as dirty. I would expect that if I make no changes to the BTM multiple select in the view the data would not be dirty, only if the adding or removing options in the multiple select would it be marked as dirty after patching.
The data does save correctly, it is just dirty, but I need to act on know if it is dirty or clean as I have _join data in my map table. The map table is named users_locations and has id, user_id, location_id and static where static is a tinyint/bool.
What I am trying to do is flag static only for newly created map table entries.
What I notice is that patchEntity is stripping the _joinData as part of the marshaling process.
So looking at the debug output below you can see that the _joinData is stripped after patching for locations and user_occupations both.
This seems undesirable to me to not know if the associated data is clean or dirty. Maybe it was intended to work this way and I am missing something. Thoughts?
In the edit form I have:
<?php echo $this->Form->input('locations._ids', ['options' => $locations]) ?>
In the controller I have:
<?php
public function edit($id = null)
{
$user = $this->Users->get($id, [
'contain' => ['Locations', 'UserOccupations']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$user = $this->Users->patchEntity($user, $this->request->data);
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
}
$securityGroups = $this->Users->SecurityGroups->find('list');
$locations = $this->Users->Locations->find('list', [
'order' => ['Locations.name' => 'ASC'],
'keyField' => 'id',
'valueField' => 'name',
'limit' => 200
]);
$userOccupations = $this->Users->UserOccupations->find('list');
$this->set(compact('user', 'securityGroups', 'locations', 'userOccupations'));
$this->set('_serialize', ['user']);
}
?>
In the model I have this in the initialize function for User:
$this->belongsToMany('Locations', [
'through' => 'Users.UsersLocations',
'foreignKey' => 'user_id',
'targetForeignKey' => 'location_id',
'className' => 'Locations.Locations'
]);
This is the request data debug output:
[
'Referer' => [
'url' => '/login'
],
'security_group_id' => '',
'username' => 'test',
'email' => 'test#test.com',
'prefix' => '',
'first_name' => 'test',
'middle_name' => '',
'last_name' => 'test',
'suffix' => '',
'credentials' => '',
'birthdate' => '',
'timezone' => 'America/New_York',
'theme' => '',
'locations' => [
'_ids' => [
(int) 0 => '7',
(int) 1 => '33'
]
],
'user_occupations' => [
'_ids' => [
(int) 0 => '1'
]
]
]
This is the user entity before patching with the request data:
object(Users\Model\Entity\User) {
'id' => '8b7197a4-5633-4bda-a6c7-a6e16f7cad64',
'identifier' => (int) 5,
'security_group_id' => null,
'sex_id' => null,
'username' => 'test',
'email' => 'test#test.com',
'prefix' => '',
'first_name' => 'test',
'middle_name' => '',
'last_name' => 'test',
'suffix' => '',
'credentials' => '',
'birthdate' => null,
'timezone' => 'America/New_York',
'theme' => '',
'ip' => '0.0.0.0',
'last_login' => null,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-16T16:17:57+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T22:22:49+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'user_occupations' => [
(int) 0 => object(Users\Model\Entity\UserOccupation) {
'id' => (int) 1,
'name' => 'Test',
'_joinData' => object(Cake\ORM\Entity) {
'user_occupation_id' => (int) 1,
'user_id' => '8b7197a4-5633-4bda-a6c7-a6e16f7cad64',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'UsersUserOccupations'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users.UserOccupations'
}
],
'locations' => [
(int) 0 => object(Locations\Model\Entity\Location) {
'id' => (int) 7,
'ldap_name' => 'Test',
'name' => 'Test',
'address' => null,
'address_2' => null,
'city' => 'Test',
'state' => 'MD',
'zip' => null,
'phone' => null,
'fax' => null,
'active' => true,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-11T19:35:34+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T21:47:29+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'_joinData' => object(Users\Model\Entity\UsersLocation) {
'location_id' => (int) 7,
'id' => (int) 304,
'user_id' => '8b7197a4-5633-4bda-a6c7-a6e16f7cad64',
'static' => false,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users.UsersLocations'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Locations.Locations'
},
(int) 1 => object(Locations\Model\Entity\Location) {
'id' => (int) 33,
'ldap_name' => 'Test2',
'name' => 'Test2',
'address' => null,
'address_2' => null,
'city' => 'Test',
'state' => 'MD',
'zip' => null,
'phone' => null,
'fax' => null,
'active' => true,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-15T21:03:46+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T21:47:29+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'_joinData' => object(Users\Model\Entity\UsersLocation) {
'location_id' => (int) 33,
'id' => (int) 305,
'user_id' => '8b7197a4-5633-4bda-a6c7-a6e16f7cad64',
'static' => false,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users.UsersLocations'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Locations.Locations'
},
],
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [
(int) 0 => 'full_name',
(int) 1 => 'name_last_first'
],
'[errors]' => [],
'[repository]' => 'Users.Users'
}
This the what the debug output look like after patching with the request data:
object(Users\Model\Entity\User) {
'id' => '8b7197a4-5633-4bda-a6c7-a6e16f7cad64',
'identifier' => (int) 5,
'security_group_id' => null,
'sex_id' => null,
'username' => 'test',
'email' => 'test#test.com',
'prefix' => '',
'first_name' => 'test',
'middle_name' => '',
'last_name' => 'test',
'suffix' => '',
'credentials' => '',
'birthdate' => null,
'timezone' => 'America/New_York',
'theme' => '',
'ip' => '0.0.0.0',
'last_login' => null,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-16T16:17:57+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T22:22:49+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'user_occupations' => [
(int) 0 => object(Users\Model\Entity\UserOccupation) {
'id' => (int) 1,
'name' => 'Test ',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users.UserOccupations'
}
],
'locations' => [
(int) 0 => object(Locations\Model\Entity\Location) {
'id' => (int) 7,
'ldap_name' => 'Test',
'name' => 'Test',
'address' => null,
'address_2' => null,
'city' => 'Test',
'state' => 'MD',
'zip' => null,
'phone' => null,
'fax' => null,
'active' => true,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-11T19:35:34+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T21:47:29+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Locations.Locations'
},
(int) 1 => object(Locations\Model\Entity\Location) {
'id' => (int) 33,
'ldap_name' => 'Test2',
'name' => 'Test2',
'address' => null,
'address_2' => null,
'city' => 'Test',
'state' => 'MD',
'zip' => null,
'phone' => null,
'fax' => null,
'active' => true,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-15T21:03:46+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T21:47:29+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Locations.Locations'
}
],
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'locations' => true,
'user_occupations' => true
],
'[original]' => [
'locations' => [
(int) 0 => object(Locations\Model\Entity\Location) {
'id' => (int) 7,
'ldap_name' => 'Test',
'name' => 'Test',
'address' => null,
'address_2' => null,
'city' => 'Test',
'state' => 'MD',
'zip' => null,
'phone' => null,
'fax' => null,
'active' => true,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-11T19:35:34+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T21:47:29+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'_joinData' => object(Users\Model\Entity\UsersLocation) {
'location_id' => (int) 7,
'id' => (int) 304,
'user_id' => '8b7197a4-5633-4bda-a6c7-a6e16f7cad64',
'static' => false,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users.UsersLocations'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Locations.Locations'
},
(int) 1 => object(Locations\Model\Entity\Location) {
'id' => (int) 33,
'ldap_name' => 'Test2',
'name' => 'Test2',
'address' => null,
'address_2' => null,
'city' => 'Test',
'state' => 'MD',
'zip' => null,
'phone' => null,
'fax' => null,
'active' => true,
'created' => object(Cake\I18n\Time) {
'time' => '2015-09-15T21:03:46+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-16T21:47:29+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'_joinData' => object(Users\Model\Entity\UsersLocation) {
'location_id' => (int) 33,
'id' => (int) 305,
'user_id' => '8b7197a4-5633-4bda-a6c7-a6e16f7cad64',
'static' => false,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users.UsersLocations'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Locations.Locations'
},
]
],
'[virtual]' => [
(int) 0 => 'full_name',
(int) 1 => 'name_last_first'
],
'[errors]' => [],
'[repository]' => 'Users.Users'
}
Please open an issue in https://github.com/cakephp/cakephp/issues.
Thanks!
I have this association in model that i want to save:
$this->belongsToMany('Tags', [
'className' => 'Tags',
'joinTable' => 'tags_associations',
'foreignKey' => 'foreign_key',
'targetForeignKey' => 'tag_id'
]);
I want to save in associated table tags_associations 2 records which have same Tags.id but different _joinData value. This is an example
object(Companies\Model\Entity\Company) {
'id' => (int) 765,
'tags' => [
(int) 0 => object(Cake\ORM\Entity) {
'id' => (int) 2,
'tag_category_id' => (int) 1,
'level' => (int) 1,
'parent_id' => (int) 1,
'lft' => (int) 2,
'rght' => (int) 135,
'tag_name' => 'Abbigliamento',
'slug' => 'abbigliamento',
'description' => null,
'created' => object(Cake\I18n\Time) {
'time' => '2015-10-04T17:56:02+0200',
'timezone' => 'Europe/Rome',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-10-05T10:03:53+0200',
'timezone' => 'Europe/Rome',
'fixedNowTime' => false
},
'_joinData' => object(Cake\ORM\Entity) {
'model' => 'Companies',
'tag_group_id' => (int) 2,
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'model' => true,
'tag_group_id' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'TagsAssociations'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => object(Cake\ORM\Entity) {
'model' => 'Companies',
'tag_group_id' => (int) 1,
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'model' => true,
'tag_group_id' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'TagsAssociations'
}
],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Tags'
},
(int) 1 => object(Cake\ORM\Entity) {
'id' => (int) 2,
'tag_category_id' => (int) 1,
'level' => (int) 1,
'parent_id' => (int) 1,
'lft' => (int) 2,
'rght' => (int) 135,
'tag_name' => 'Abbigliamento',
'slug' => 'abbigliamento',
'description' => null,
'created' => object(Cake\I18n\Time) {
'time' => '2015-10-04T17:56:02+0200',
'timezone' => 'Europe/Rome',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-10-05T10:03:53+0200',
'timezone' => 'Europe/Rome',
'fixedNowTime' => false
},
'_joinData' => object(Cake\ORM\Entity) {
'model' => 'Companies',
'tag_group_id' => (int) 2,
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'model' => true,
'tag_group_id' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'TagsAssociations'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => object(Cake\ORM\Entity) {
'model' => 'Companies',
'tag_group_id' => (int) 1,
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'model' => true,
'tag_group_id' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'TagsAssociations'
}
],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Tags'
}
],
}
In my example only second entity is kept in tags_association table.
You use use the through option from http://book.cakephp.org/3.0/en/orm/associations.html#using-the-through-option
class StudentsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Courses', [
'through' => 'CourseMemberships',
]);
}
}
class CoursesTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Students', [
'through' => 'CourseMemberships',
]);
}
}
class CoursesMembershipsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Students');
$this->belongsTo('Courses');
}
}
Now the CourseMemberships model is a individual model, and you can create as many as you want.
Finally, I have resolved with another association
$this->belongsToMany('Tags', [
'className' => 'Tags',
'joinTable' => 'tags_associations',
'foreignKey' => 'foreign_key',
'targetForeignKey' => 'tag_id',
]);
$this->belongsToMany('TagsSecondary', [
'className' => 'Tags',
'joinTable' => 'tags_associations',
'foreignKey' => 'foreign_key',
'targetForeignKey' => 'tag_id',
'saveStrategy' => 'append'
]);
First i save Tags with tag_group_id = 1 with Tags Associations and after those with tag_group_id = 2 with TagsSecondary, that has append as saveStrategy.
You can load the model in your controller
$this->loadModel('tagAssociations');
and after that you can create new entity from this table, patch it and finally save it.
//saving tags
$tagAssociation = $this->tagAssociations->newEntity();
//$handling $data
$tagAssociation = $this->tagAssociations->patchEntity($tagAssociation , $data);
$this->tagAssociations->save($tagAssociation );
It's the best solution i have found.