CakePHP 3: Collection: listNested - cakephp

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']
]

Related

Laravel Collection : Remove collection grouping after groupBy()

collect($vars)->groupBy() returns in the format
[
'catgeory' => [
['id' => 1, 'name' => 'name1', 'type' => 'category'],
['id' => 2, 'name' => 'name4', 'type' => 'category'],
]
'name' => ['id' => 3, 'name' => 'name2', 'type' => 'name'],
'value' => ['id' => 4, 'name' => 'name3', 'type' => 'value']
]
What is the best way to change to like this, removing
the grouping but preferrably using the collections itself
[
['id' => 1, 'name' => 'name1', 'type' => 'category'],
['id' => 2, 'name' => 'name4', 'type' => 'category'],
['id' => 3, 'name' => 'name2', 'type' => 'name'],
['id' => 4, 'name' => 'name3', 'type' => 'value'],
]
Original array is this:
[
['id' => 3, 'name' => 'name2', 'type' => 'name'],
['id' => 1, 'name' => 'name1', 'type' => 'category'],
['id' => 2, 'name' => 'name4', 'type' => 'category'],
['id' => 4, 'name' => 'name3', 'type' => 'value'],
]
By using flatten() with a depth of one.
collect($vars)->groupBy('type')->flatten(1);

Update to value in _joinData works but misses right index

I'm finally finding out how to update a join table after a lot of work, but I found something that I definitely cannot solve. I added manually a set of checkboxes to update an item on my join table and work almost perfectly, as long as they're part of my legacy database. When I add new items to the join table with lower id, then the wrong item on my join table is updated.
Here is how I have my controller:
public function edit($id)
{
$brigada = $this->Brigadas
->findById($id)
->contain(['Tags', 'Voluntarios'])
->firstOrFail();
if ($this->request->is(['patch', 'post', 'put'])) {
// https://github.com/cakephp/cakephp/issues/7864
if (!empty($this->request->data['voluntarios']['_ids'])) {
$vol = [];
$ids = [];
foreach ($this->request->data['voluntarios']['_ids'] as $key=>$locationId) {
$vol['_ids'][$key] = $locationId;
$vol[] = [
'_joinData' => [
'lider' => isset($this->request->data['voluntarios'][$key]['_joinData']['lider']) ? $this->request->data['voluntarios'][$key]['_joinData']['lider'] : false
]
];
}
}
if (isset($vol)){
$this->request->data['voluntarios'] = $vol;
}
$this->Brigadas->patchEntity($brigada, $this->request->data, ['associated' => ['Voluntarios._joinData', 'Participaciones', 'Participaciones._joinData']]);
if ($this->Brigadas->save($brigada)) {
$this->Flash->success(__('Your team data has been updated.'));
return $this->redirect(['action' => 'view', $id]);
}
$this->Flash->error(__('Unable to update your team data.'));
}
}
This is how I iterated my form. Noticed that I added an extra hidden form for id just to try changing the id numbers. But the result is exactly the same in the end.
foreach($all_vol as $key=>$voluntario){
echo $this->Form->control('voluntarios.'.$key.'.id', ['hidden' => true]);
echo $this->Form->control('voluntarios.'.$key.'._joinData.lider', [
'label' => $voluntario->full_name,
'default'=>'0',
]);
}
Here is the request sent by the form.
[
'_ids' => [
(int) 0 => '1',
(int) 1 => '2',
(int) 2 => '184',
(int) 3 => '303',
(int) 4 => '1838',
(int) 5 => '1873',
(int) 6 => '2553',
(int) 7 => '4467',
(int) 8 => '4973',
(int) 9 => '5148',
(int) 10 => '5493',
(int) 11 => '6307',
(int) 12 => '7055',
(int) 13 => '7467',
(int) 14 => '7561',
(int) 15 => '7567',
(int) 16 => '7864'
],
(int) 0 => [
'id' => '184',
'_joinData' => [
'lider' => '0'
]
],
(int) 1 => [
'id' => '303',
'_joinData' => [
'lider' => '0'
]
],
(int) 2 => [
'id' => '1838',
'_joinData' => [
'lider' => '0'
]
],
(int) 3 => [
'id' => '1873',
'_joinData' => [
'lider' => '0'
]
],
(int) 4 => [
'id' => '2553',
'_joinData' => [
'lider' => '0'
]
],
(int) 5 => [
'id' => '4467',
'_joinData' => [
'lider' => '0'
]
],
(int) 6 => [
'id' => '4973',
'_joinData' => [
'lider' => '0'
]
],
(int) 7 => [
'id' => '5148',
'_joinData' => [
'lider' => '0'
]
],
(int) 8 => [
'id' => '5493',
'_joinData' => [
'lider' => '0'
]
],
(int) 9 => [
'id' => '6307',
'_joinData' => [
'lider' => '0'
]
],
(int) 10 => [
'id' => '7055',
'_joinData' => [
'lider' => '0'
]
],
(int) 11 => [
'id' => '7467',
'_joinData' => [
'lider' => '0'
]
],
(int) 12 => [
'id' => '7561',
'_joinData' => [
'lider' => '0'
]
],
(int) 13 => [
'id' => '7567',
'_joinData' => [
'lider' => '0'
]
],
(int) 14 => [
'id' => '7864',
'_joinData' => [
'lider' => '0'
]
],
(int) 15 => [
'id' => '1',
'_joinData' => [
'lider' => '1'
]
],
(int) 16 => [
'id' => '2',
'_joinData' => [
'lider' => '1'
]
]
]
I added items with id 1 and 2 to the bottom and turned them into 'lider', but when they're passed to patchEntity, two different items are selected as 'lider'. Here is the request after editing it manually.
[
(int) 0 => [
'id' => '1',
'_joinData' => [
'lider' => '1'
]
],
(int) 1 => [
'id' => '2',
'_joinData' => [
'lider' => '1'
]
],
(int) 2 => [
'id' => '184',
'_joinData' => [
'lider' => '0'
]
],
(int) 3 => [
'id' => '303',
'_joinData' => [
'lider' => '0'
]
],
(int) 4 => [
'id' => '1838',
'_joinData' => [
'lider' => '0'
]
],
(int) 5 => [
'id' => '1873',
'_joinData' => [
'lider' => '0'
]
],
(int) 6 => [
'id' => '2553',
'_joinData' => [
'lider' => '0'
]
],
(int) 7 => [
'id' => '4467',
'_joinData' => [
'lider' => '0'
]
],
(int) 8 => [
'id' => '4973',
'_joinData' => [
'lider' => '0'
]
],
(int) 9 => [
'id' => '5148',
'_joinData' => [
'lider' => '0'
]
],
(int) 10 => [
'id' => '5493',
'_joinData' => [
'lider' => '0'
]
],
(int) 11 => [
'id' => '6307',
'_joinData' => [
'lider' => '0'
]
],
(int) 12 => [
'id' => '7055',
'_joinData' => [
'lider' => '0'
]
],
(int) 13 => [
'id' => '7467',
'_joinData' => [
'lider' => '0'
]
],
(int) 14 => [
'id' => '7561',
'_joinData' => [
'lider' => '0'
]
],
(int) 15 => [
'id' => '7567',
'_joinData' => [
'lider' => '0'
]
],
(int) 16 => [
'id' => '7864',
'_joinData' => [
'lider' => '0'
]
],
'_ids' => [
(int) 0 => '1',
(int) 1 => '2',
(int) 2 => '184',
(int) 3 => '303',
(int) 4 => '1838',
(int) 5 => '1873',
(int) 6 => '2553',
(int) 7 => '4467',
(int) 8 => '4973',
(int) 9 => '5148',
(int) 10 => '5493',
(int) 11 => '6307',
(int) 12 => '7055',
(int) 13 => '7467',
(int) 14 => '7561',
(int) 15 => '7567',
(int) 16 => '7864'
]
]
In the end, it doesn't matter whether I edit manually or not, since I get this on my patchEntity call. Notice that 'id' isn't neither 1 or 2:
'_joinData' => object(App\Model\Entity\Participacione) {
'id' => (int) 981,
'brigada_id' => (int) 222,
'voluntario_id' => (int) 7567,
'lider' => true,
'[new]' => false,
'[accessible]' => [
'brigada_id' => true,
'voluntario_id' => true,
'lider' => true,
'brigada' => true,
'voluntario' => true
How can I solve this? I've posted other questions without many answers, but I'll keep having faith in humanity to sort this out. Thanks in advance for reading my question!
Surprisingly I managed to solve it by using a sort attribute in my table. Like this:
$this->belongsToMany('Voluntarios', [
'foreignKey' => 'brigada_id',
'targetForeignKey' => 'voluntario_id',
'joinTable' => 'participaciones',
'saveStrategy' => 'replace',
'through' => 'Participaciones',
'sort' => ['Voluntarios.id' => 'ASC'],
]);
I feel like everything is pretty forced, but I couldn't find a nicer way to do it. I'm noticing a few bugs by the time I delete a starred value, but it's a step in the right direction.

CakePHP 3: issue saving hasMany associations

I'm having trouble saving and updating hasMany associations. It seems that Cake can't patch the entity correctly somehow.
ProductGroups Table:
$this->hasMany('ProductIdentities', [
'foreignKey' => 'product_group_id',
'saveStrategy' => 'replace'
]);
ProductIdentities Table:
$this->belongsTo('ProductGroups', [
'foreignKey' => 'product_group_id'
]);
ProductGroupsController:
// patch entity
$productGroup = $this->ProductGroups->patchEntity($productGroup, $this->request->data, [
'associated' => [
'StaffMembers' => ['onlyIds' => true],
'ProductIdentities' => ['onlyIds' => true]
]
]);
$this->request->data:
[
'group_name' => 'Creative and Performing Arts',
'active' => '1',
'product_type_id' => '2',
'staff_members' => [
'_ids' => [
(int) 0 => '103',
(int) 1 => '11',
(int) 2 => '3'
]
],
'product_identities' => [
'_ids' => [
(int) 0 => '1760',
(int) 1 => '1762',
(int) 2 => '1763',
(int) 3 => '1764'
]
]
]
edit.ctp
<?php
echo $this->Form->input('group_name',
['class' => 'form-control']);
echo $this->Form->input('active',
['class' => 'form-control']);
echo $this->Form->input('product_type_id',
['class' => 'form-control']);
echo $this->Form->input('staff_members._ids',
['options' => $staffMembers,
'class' => 'form-control height-200']);
echo $this->Form->input('product_identities._ids',
['options' => $productIdentities,
'class' => 'form-control height-500']);
?>
If a productGroup is not associated with any productIdentities, it saves fine. However if you want to select more productIdentities or unselect some existing ones, the data won't be saved. Instead, Cake will create new records in product_identites table.
After doing a debug, I could see that Cake did not patch entity correctly. See below:
'product_identities' => [
(int) 0 => object(App\Model\Entity\ProductIdentity) {
(int) 0 => '1760',
(int) 1 => '1762',
(int) 2 => '1763',
(int) 3 => '1764',
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
(int) 0 => true,
(int) 1 => true,
(int) 2 => true,
(int) 3 => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'ProductIdentities'
}
],
The onlyIds option is supposed to stop Cake from creating new records but obviously it didn't work.
And I've formatted my data into the format suggested in Cake's documentation so I'm very confused.
$data = [
'title' => 'My new article',
'body' => 'The text',
'user_id' => 1,
'comments' => [
'_ids' => [1, 2, 3, 4]
]
];
http://book.cakephp.org/3.0/en/orm/saving-data.html#converting-hasmany-data
Any comments/help is appreciated.

How to get only selected fields from rightJoin() and contain() in cakephp3

cakephp 3.2.3x
I am trying to show custom fields from right join which is contained (left joined) with an other table and it is not working. How ever contain custom fields is working.
Note 1: When I get rid of contain() my ORM works as I am expecting.
$properties = $this->find()
->select([
'Property.id', 'Property.company_id', 'Property.address1', 'Property.postcode',
'Tenancies.property_id', 'Tenancies.start_date', 'Tenancies.end_date', 'Tenancies.deposit_total',
])
->rightJoin(['Tenancies' => 'tenancy'],[
'Tenancies.property_id = Property.id',
'Tenancies.active = 1'
])
->contain([
'Tenancies.Tenants' => function($q) {
return $q
->select([
'Tenants.id', 'Tenants.stage', 'Tenants.tenancy_id', 'Tenants.holding_fee',
])
->where([
'active = 1',
]);
}
])
->where(['Property.active = 1', $conditions]);
Note 2: Generated sql is correct but it is not applying to the query.
'SELECT Property.id AS Property__id, Property.company_id AS
Property__company_id, Property.address1 AS Property__address1,
Property.postcode AS Property__postcode,
Tenancies.property_id AS Tenancies__property_id,
Tenancies.start_date AS Tenancies__start_date,
Tenancies.end_date AS Tenancies__end_date,
Tenancies.deposit_total AS Tenancies__deposit_total FROM
property Property RIGHT JOIN tenancy Tenancies ON
(Tenancies.property_id = Property.id AND Tenancies.active = 1) WHERE
(Property.active = 1 AND Property.company_id = :c0)'
The query should show me only 3 fields of 'Tenancies' but it retrieves all the Tenancies fields.
(int) 0 => [
'id' => (int) 102,
'company_id' => (int) 3,
'address1' => 'Grace Dieu Court',
'postcode' => 'LE11 4QH',
'tenancies' => [
(int) 0 => [
'id' => (int) 16,
'property_id' => (int) 102,
'landlord_id' => (int) 65,
'agent_id' => (int) 7,
'company_id' => (int) 3,
'bedroom' => (int) -1,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2015-05-08T09:30:41+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'active' => true,
...
... ## } The rest of all fileds
'tenants' => [
(int) 0 => [
'id' => (int) 16,
'stage' => (int) 7,
'tenancy_id' => (int) 16,
'holding_fee' => (float) 50
],
(int) 1 => [
'id' => (int) 17,
'stage' => (int) 7,
'tenancy_id' => (int) 16,
'holding_fee' => (float) 50
]
]
]
]
],

CakePHP filter a deep associated find

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]);
}
]);

Resources