how to save belongstomany checkboxes in cakephp - cakephp

I have a set of checkboxes with a belongs to many relationship. I couldnt use a multiple checkbox option for various reasons. The table Tutors has a belongsToMany relationship with subjects table. I get the correct checkboxes checked when it loads and when I update the checboxes I get the correct checkbox data returned but how do i save this? The docs say i need to put the id's in an array for the correct Subjects to be checked . I tried a few things but I couldnt save the data. I dont get an error, just not saving.
//view
foreach ($subjects as $key => $item) {
$sub=$item->name;
$val=0;
foreach ($tutor->subjects as $key2 => $item2) {
$sub2=$item2->name;
if ($sub==$sub2){
$val=1;
break;
}
}
echo $this->Form->hidden('subjects.'.$key.'.id', ['value'=>$item->id]);
echo $this->Form->input('subjects.'.$key.'.checkedvalue', ['label'=>$sub,
'checked'=> $val,'type'=>'checkbox']);
}
//controller
public function edittest5($id = null)
{
$tutor = $this->Tutors->get($id, [
'contain' => ['AvailabilityForTutors','Subjects']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$tutor = $this->Tutors->patchEntity($tutor, $this->request->data(),['associated' => ['AvailabilityForTutors','Subjects']] );
// $tutor->dirty('availability_for_tutors', true);
debug($tutor);
$this->Tutors->save($tutor,[
// 'atomic'=>false,
'validate' => false,
'associated' => ['Subjects']
]);
return $this->redirect(['action' => 'edittest5',$id]);
}
$subjects = $this->Tutors->Subjects->find('all', ['limit' => 200]);
returned data
'first_name' => 'drew',
'subjects' => [
(int) 0 => [
'id' => '1',
'checkedvalue' => '1'
],
(int) 1 => [
'checkedvalue' => '0'
],
(int) 2 => [
'checkedvalue' => '0'
],
(int) 3 => [
'checkedvalue' => '0'
],
(int) 4 => [
'id' => '5',
'checkedvalue' => '1'

Related

Using the paginator directly is not recognising the configuration

CakePHP Version: 4.0.1
Introduction
I have 2 methods that both use the index view, index and search. On index the column can be selected from a select list and a value can be inputted via an input form control enabling a search by column and value. This data is sent via GET to the search method where empty values are checked and the query is executed and the index view is rendered.
In the later 3x versions with the below configuration the index view had the sort on the selected column which is what it is meant to do.
IE: Index view has due_date sorted on the initial load and I select task_name then submit the form to the search method. The task_name has the sort when the view is rendered.
TASKS CONTROLLER
Public pagination property:
public $paginate = [
'sortWhitelist' => [
'Tasks.due_date',
'Tasks.task_name',
'Tasks.type',
'Tasks.priority',
'Tasks.related_to_name',
'Contacts.first_name',
'Contacts.last_name',
'Accounts.account_name',
'Tasks.task_desc'
]
];
Search Method
I initialise the data received from the index method and apply the config to the pagination property and send the query object to the view.
$this->setPage('');
$this->setSort($this->request->getQuery('column'));
$this->setDirection('asc');
// Validation of the page, sort, direction and limit is done here.
// IE: The $this->getSort() must be a string and not be numeric and has a strlen check
// and the $this->getDirection() can only be a string with values 'asc' or 'desc' etc.
if (!empty($this->getPage())) {
$this->paginate['page'] = $this->getPage();
}
$this->paginate['sort'] = $this->getSort();
$this->paginate['direction'] = $this->getDirection();
$this->paginate['limit'] = $this->getLimit();
debug($this->paginate);
$tasks = $this->paginate($query);
$this->set(compact('tasks'));
The result of debug is:
[
'sortWhitelist' => [
(int) 0 => 'Tasks.due_date',
(int) 1 => 'Tasks.task_name',
(int) 2 => 'Tasks.type',
(int) 3 => 'Tasks.priority',
(int) 4 => 'Tasks.related_to_name',
(int) 5 => 'Contacts.first_name',
(int) 6 => 'Contacts.last_name',
(int) 7 => 'Accounts.account_name',
(int) 8 => 'Tasks.task_desc'
],
'sort' => 'Tasks.task_name',
'direction' => 'asc',
'limit' => (int) 25
]
Result
The sort is on the task_name.
A couple of months ago I upgraded to 4 and have just revisted this functionality to find the sort is on the column that was present on index and not the column that was selected. I tried the below to fix the problem:
I referenced this information in the cookbook. And this from SO.
$config = $this->paginate = [
'page' => $this->getPage(),
'sort' => $this->getSort(),
'direction' => $this->getDirection(),
'limit' => $this->getLimit()
];
debug($config);
$tasks = $this->Paginator->paginate($query, $config);
debug($this->Paginator);
$this->set(compact('tasks'));
The result of debug $config is:
[
'page' => '',
'sort' => 'Tasks.task_name',
'direction' => 'asc',
'limit' => (int) 25
]
The result of debug $this->Paginator is:
object(Cake\Controller\Component\PaginatorComponent) {
'components' => [],
'implementedEvents' => [],
'_config' => [
'page' => (int) 1,
'limit' => (int) 20,
'maxLimit' => (int) 100,
'whitelist' => [
(int) 0 => 'limit',
(int) 1 => 'sort',
(int) 2 => 'page',
(int) 3 => 'direction'
]
]
}
NOTE: The whitelist contains limit, sort, page and direction? And the limit is 20 and I don't even have a selection of 20?
Result
The sort is on the due_date and I need it on the task_name.
Extra Info
If I then click the sort on task_name the sort is on the task_name. All the sorts work just not on the initial load?
Question
How can I configure the pagination property so the sort is on the task_name from the initial load of the search method.
Thanks Z.
The fix is a bit costly and not ideal but it does work. I do a redirect on the initial load. Basically submit the form to search then redirect back to search. IE:
if ($this->request->getQuery('initial') === 'yes') {
$redirect = $this->request->getQuery('redirect', [
'action' => 'search',
'?' => [
'method' => 'search',
'column' => $this->getColumn(),
'input' => $this->getInput(),
'page' => $this->getPage(),
'sort' => $this->getSort(),
'direction' => $this->getDirection(),
'limit' => $this->getLimit(),
'filter' => $this->getFilter(),
]
]);
return $this->redirect($redirect);
exit(0);
}
$config = $this->paginate = [
'sortWhitelist' => [
'Tasks.due_date',
'Tasks.task_name',
'Tasks.type',
'Tasks.priority',
'Tasks.related_to_name',
'Contacts.first_name',
'Contacts.last_name',
'Accounts.account_name',
'Tasks.task_desc'
],
'page' => $this->getPage(),
'sort' => $this->getSort(),
'direction' => $this->getDirection(),
'limit' => $this->getLimit()
];
$tasks = $this->Paginator->paginate($query, $config);
$this->set(compact('tasks'));
The sort is now on the task_name.
This negates the initial load problem and simulates usage after the page initially loads where I know the sorts work.

How to insert a new record in an associated belongsTo table instead of editing the associated one

In a edit-template, I created 2 forms. The first to edit a record from 'Table1' and a second form to add an record to the associated (belongsTo) table 'Table2'. I don't want to edit the associated record but add a new one in Table2 and change the bindingkey of the record from 'Table1'.
In the controller I use patchEntity(), but this way the original linked record in 'Table2' is edited.
The code for the second form look like this.
<?= $this->Form->create($machine, ['url' => ['action' => 'edit_foto']]);?>
<?= $this->Form->controls([
'id' => ['type' => 'hidden'],
'foto.omschrijving' => [
'type' => 'text',
'label' => __('naam van de machine'),
'value' => $result->type_machine
], []);?>
<?= $this->Form->button(__('save'), ['type' => 'submit']); ?>
<?= $this->Form->end(); ?>
In the controller for 'Table1' I use something like this.
public function editFoto($id) {
$data = $this->getRequest()->getData();
$machine = $this->Machines->get($id, [
'contain' => [
'Foto'
]
]);
if ($this->request->is([
'patch',
'post',
'put'
])) {
$machine = $this->Machines->patchEntity($machine, $data);
if ($this->Machines->save($machine)) {
$this->Flash->success(__('Machine {naam} has been saved.', [
'naam' => $machine['naam']
]));
return $this->redirect(['action' => 'edit', $id]);
}
}
}
How can I prevent the controller from editing the original record and instead force to add a new record in 'Table2' and change the bindingkey in 'Table1'?
Help is much appreciated.
added information:
the data send by the form looks like:
[
'id' => '87',
'foto' => [
'omschrijving' => 'WAKER 135 - ter - testje',
'foto' => [
'tmp_name' => 'C:\xampp\tmp\php6CFD.tmp',
'error' => (int) 0,
'name' => 'MAKITA DTD153RTJ accuschroevendraaier.jpg',
'type' => 'image/jpeg',
'size' => (int) 188971
],
'dir_id' => '5c9dd428cc4ef'
]
]
Instead of
$machine = $this->Machines->patchEntity($machine, $data);
$this->Machines->save($machine);
you should use
$machine = $this->Machines->newEntity($data, [
'associated' => ['Fotos']
]);
$this->Machines->save($machine);
to save the data. If the associations between Table1 and Table2 are correct in the models, this will also update the binding keys.
Make sure, the format of the ids in FormHelper is correct. See the docs, how to handle Saving With Associations. You can see this in $data. When submitting the form, $data should be in the following format:
$data = [
'id' => '...',
'foto' => [
// ... model data
],
];

How to filter containments by associated data?

I want to get associated only if the second level associated data respect the given condition.
The query will maybe better explain what I try to do.
$selSite = $this->Sites->get($selSiteId, [
'contain' => [
'Agplans.Products' => function ($q) {
return $q
->where([
'Products.type' => 'hosting',
]);
}
]
]);
So I expect an agplan only if its associated product matches the condition.
But the result is:
'agplans' => [
(int) 0 => object(App\Model\Entity\Agplan) {
'id' => (int) 20,
'product_id' => (int) 4,
'product' => null,
...,
},
(int) 1 => object(App\Model\Entity\Agplan) {
'id' => (int) 21,
'site_id' => (int) 64,
'product_id' => (int) 75,
'product' => object(App\Model\Entity\Product) {
'id' => (int) 75,
...,
},
...,
}
],
My problem here is to get agplan[0] with a product => null.
It's not what I understood from the doc.
How to get the agplan with 'product' => object(App\Model\Entity\Product) only?
So thanks to what #ndm pointed me out I understood that I have to build my query like that:
$selSite = $this->Sites->get($selSiteId, [
'contain' => [
'Agplans.Products',
'Agplans' => function ($q) {
return $q
->matching('Products', function ($q) {
return $q
->where([
'type' => 'hosting',
]);
});
}
]
]);
That way, I only get agplans matching the given product.
Anyway, I find the CakePHP doc not clear on the contain feature about restricting associated.

CakePHP 3: How to update foreignKey?

I use BlueImp jQuery plugin to upload multiple images in follow scenario:
User open add or edit Albums page, select images and procces upload via ajax method. This work fine, image data (name, size, model, field,..) are stored in related images table without foreign key.
Ajax return id of recent uploaded image, and js create (captions, position) inputs for that file.
User fill other form fields, and click submit.
Problem, if user create new album or edit/update exist one wich not contain images, application does not update foriegn key for new images. But if album contain images, and user add new one to album, after save post cakephp add / update foreign key to new images records.
Debug output:
// first time add images, does not update FK
[
'name' => 'A4',
'images' => [
(int) 116 => [
'caption' => '',
'position' => '1',
'id' => '116'
]
],
'id' => '4',
'user_id' => '1',
'active' => '1'
]
// album has one image, we add new one, CakePHP Update FK for new images
[
'name' => 'A4',
'images' => [
(int) 117 => [
'caption' => '',
'position' => '1',
'id' => '117'
],
(int) 118 => [
'caption' => '',
'position' => '2',
'id' => '118'
]
],
'id' => '4',
'user_id' => '1',
'active' => '1'
]
AlbumsController add method:
public function add()
{
$album = $this->Albums->newEntity();
if ($this->request->is('post')) {
$album = $this->Albums->patchEntity($album, $this->request->data,['associated' => ['Images']]);
if ($this->Albums->save($album)) {
$this->Flash->success(__('The album has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The album could not be saved. Please, try again.'));
}
}
$users = $this->Albums->Users->find('list', ['limit' => 200]);
$this->set(compact('album', 'users'));
$this->set('_serialize', ['album']);
}
Albums Table:
$this->hasMany('Images', [
'foreignKey' => 'foreign_key',
'conditions' => [
'Images.model' => 'Albums',
'Images.field' => 'upload'
],
'sort' => ['Images.position' => 'ASC']
]);
Images Table:
$this->belongsTo('Albums', [
'foreignKey' => 'foreign_key',
'joinType' => 'INNER'
]);
I use same approach in the cakephp 2 apps, and work without problems.
Video: http://screencast-o-matic.com/watch/cDhYYOinCX
Q: How to update foreignKey?
I'm not sure is this correct way, but it's work for me.
AlbumsTable
public function afterSave(Event $event, Entity $entity, ArrayObject $options)
{
if (isset($entity)) {
$images = TableRegistry::get('Images');
$images->query()
->update()
->set(['foreign_key' => $entity->id])
->where([
'foreign_key IS NULL',
'model' => 'Albums'
])
->execute();// conditions
}
}

cakephp 3.x saving multiple entities - newEntities

I'm having the hardest time with saving multiple records. I've tried a million things, but I end up with the same problem: my records are not saved and I can't see any errors. Bear in mind that I'm new to cakephp and a novice coder.
Am I missing something obvious and crucial?
Table:
$this->table('splits');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Transactions', [
'foreignKey' => 'transaction_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Accounts', [
'foreignKey' => 'account_credit_id',
'joinType' => 'INNER'
]);
Controller:
$splits = $this->Splits->newEntity();
if ($this->request->is('post')) {
$splits = $this->Splits->newEntities($this->request->data());
debug($splits);
foreach ($splits as $split){
$this->Splits->save($split);
}
}
$transactions = $this->Splits->Transactions->find('list', ['limit' => 200]);
$accounts = $this->Splits->Accounts->find('list', ['limit' => 200]);
$this->set(compact('split', 'transactions', 'accounts'));
$this->set('_serialize', ['split']);
Template:
echo $this->Form->input('Splits.1.transaction_id', ['options' => $transactions]);
echo $this->Form->input('Splits.1.amount', ['type' => 'float']);
echo $this->Form->input('Splits.1.account_id', ['options' => $accounts]);
echo $this->Form->input('Splits.2.transaction_id', ['options' => $transactions]);
echo $this->Form->input('Splits.2.amount', ['type' => 'float']);
echo $this->Form->input('Splits.2.account_id', ['options' => $accounts]);
echo $this->Form->input('Splits.3.transaction_id', ['options' => $transactions]);
echo $this->Form->input('Splits.3.amount', ['type' => 'float']);
echo $this->Form->input('Splits.3.account_id', ['options' => $accounts]);
Debug on $splits:
[
(int) 0 => object(App\Model\Entity\Split) {
(int) 1 => [
'transaction_id' => '108',
'amount' => '100.33',
'account_id' => '2'
],
(int) 2 => [
'transaction_id' => '108',
'amount' => '50.22',
'account_id' => '4'
],
(int) 3 => [
'transaction_id' => '108',
'amount' => '65.22',
'account_id' => '5'
],
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
(int) 1 => true,
(int) 2 => true,
(int) 3 => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Splits'
}
]
Did you somewhere saw this Table.index.field style being used, or did you just tried something and hoped it would work?
When saving many records respectively creating many entities, the expected format is a numerically indexed array that holds the data for the individual records, just as shown in the docs
Cookbook > Database Access & ORM > Saving Data > Converting Multiple Records
When creating forms that create/update multiple records at once you
can use newEntities():
[...]
In this situation, the request data for multiple articles should look
like:
$data = [
[
'title' => 'First post',
'published' => 1
],
[
'title' => 'Second post',
'published' => 1
],
];
So your inputs should not use the table name, but just the index and the field name, like
echo $this->Form->input('0.transaction_id', /* ... */);
echo $this->Form->input('0.amount', /* ... */);
echo $this->Form->input('0.account_id', /* ... */);
echo $this->Form->input('1.transaction_id', /* ... */);
echo $this->Form->input('1.amount', /* ... */);
echo $this->Form->input('1.account_id', /* ... */);
echo $this->Form->input('2.transaction_id', /* ... */);
echo $this->Form->input('2.amount', /* ... */);
echo $this->Form->input('3.account_id', /* ... */);
Try this:
$splits = TableRegistry::get('splits');
$entities = $splits->newEntities($this->request->data());
foreach ($entities as $entity) {
$splits->save($entity);
}
Try this example to insert multiple recored in cakphp 3.x
$passwords = $this->request->data('password_id');
foreach ($passwords as $password) {
$data = [
'event_id' => $this->request->data('event_id'),
'password_id' => $password
];
$eventPasswordAll = $this->EventPasswordAll->newEntity();
$this->EventPasswordAll->patchEntity($eventPasswordAll, $data);
$this->EventPasswordAll->save($eventPasswordAll );}
I hope it is useful to your question
please reviwe it, Thanks !
If you’d like to process all the entities as a single transaction you can use transactional():
Although using a loop, "transations" avoids errors if the "save" does not work.
// In a controller.
$articles->getConnection()->transactional(function () use ($articles, $entities) {
foreach ($entities as $entity) {
$articles->save($entity, ['atomic' => false]);
}
});
Converting Multiple Records - CakePHP 3

Resources