CakePHP 3: ->find('all') with contain not populating with associated data - cakephp

I'm attempting to ->find('all') & contain the hasMany() Schedules which belongsTo() Services within the Services controller index method. A Service can actually have zero to many Schedules. Testing and experience from the Services view method has lead me believe that the issue is due to a DB field that is using a keyword. It's a field that I cannot see a need for at this time. Unfortunately, I don't have the ability to change database structure.
I attempted ->contain() and specified the fields I wanted, including the primary fields of the Schedules contain. The results were the same as Attempt #1 listed below. I have successfully used that in another model.
I attempted ->selectAllExcept($tableObj, ['FieldToExclude'])->contain(). I also attempted a beforeFind() within the SchedulesTable.php after research here but the function never seemed to be called.
I'm able to circumvent the issue in the view method by specifying the fields in a find directly to the Services table. However, this is not a viable option within the index method. I can do a loop to get the related records, but that's timely and defeats the fantastic benefits of Cake. Contain is awesome and is exactly what I need here.
Are the belongsTo & hasMany set up incorrectly? Am I missing something in the coding of $query? I've read through the documentation and I know that I am missing what is probably obvious to so many. I know I am getting to the point of frustration that won't let me think clearly. ;) I've even stepped away for a day hoping that would help.
Thank you in advance for any help that can be provided. It will be greatly appreciated.
ServicesTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('OSCL');
$this->setDisplayField('DocNum');
$this->setPrimaryKey('DocNum');
$this->belongsTo('Locations', [
'className' => 'Locations',
'bindingKey' => 'Address',
'foreignKey' => 'BPShipCode',
'joinType' => 'INNER'
]
);
$this->hasMany('Schedules', [
'className' => 'Schedules',
'foreignKey' => 'SrcvCallID',
'bindingKey' => 'CallID'
]
);
}
And SchedulesTable.php
use Cake\Event\Event;
use ArrayObject;
class SchedulesTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('SCL6');
$this->setDisplayField('Technician');
$this->setPrimaryKey(['SrcvCallID', 'Line']);
$this->belongsTo('Services', [
'className' => 'Services',
'foreignKey' => 'SrcvCallID',
'bindingKey' => 'CallID'
]
);
$this->hasOne('Employees', [
'className' => 'Employees',
'foreignKey' => 'empID',
'bindingKey' => 'Technician'
]
);
}
/*
Added to class after the original 3 attempts.
Re-attempted the queries. Function made no difference.
Debug & die within the function showed that the function was never called.
*/
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
return $query->selectAllExcept($this, ['Close']);
}
}
Attempt #1 returns the expected # of records but the schedules array is empty. I don't get any error messages and the query log doesn't show any attempts to access the Schedules table.
$query = $this->Services->find('all')
->where($conditions)
->order([$sort => $direction])
->contain(['Schedules']);
Debug of $query->toArray(); The table has over 100 fields. I've removed many here for visual ease.
\src\Controller\ServicesController.php (line 124)
[
(int) 0 => object(App\Model\Entity\Service) {
'callID' => (int) 361893,
'subject' => 'Printer jam',
'customer' => 'C202044',
'custmrName' => 'Some Company',
'contctCode' => (int) 986,
'manufSN' => '',
'internalSN' => '16J151800861',
'createDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-17 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'createTime' => (int) 1612,
'closeDate' => null,
'closeTime' => null,
'DocNum' => (int) 316939,
'Series' => (int) 30,
'schedules' => [],
'[new]' => false,
'[accessible]' => [],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[hasErrors]' => false,
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Services'
},
]
Query Log
SELECT
Services.callID AS [Services__callID], Services.subject AS [Services__subject], Services.customer AS [Services__customer], Services.custmrName AS [Services__custmrName], ...
FROM
OSCL Services
WHERE
(
Services.status = 7
OR Services.status = -3
)
ORDER BY Services.DocNum DESC OFFSET 0 ROWS FETCH FIRST 20 ROWS ONLY
SELECT
(
COUNT(*)
) AS [count]
FROM
OSCL Services
WHERE
(
Services.status = 7
OR Services.status = -3
)
Attempt #2 generates a SQL query error "Error: SQLSTATE[42000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Incorrect syntax near the keyword 'Close'". Makes sense because the default behavior for a model is to select all of the fields.
$query = $this->Services->find('all')
->where($conditions)
->order([$sort => $direction])
->select($this->Services->Schedules);
Attempt #3. I tried to pass the table object to ->selectAllExcept() and exclude the problem field. The query builder builds the query incorrectly by using the fields from the Schedules table as if they were part of the Services table
$query = $this->Services->find('all')
->where($conditions)
->order([$sort => $direction])
->selectAllExcept($this->Services->Schedules, ['Close'])
->contain(['Schedules']);
Query built by the query builder. SrcvCallID, Line & Technician are all fields of the Schedules table, not Services.
SELECT
Services.SrcvCallID AS [Services__SrcvCallID], Services.Line AS [Services__Line], Services.Technician AS [Services__Technician], ...
FROM
OSCL Services
WHERE
(Services.status = :c0 OR Services.status = :c1)
ORDER BY
Services.DocNum DESC
OFFSET 0 ROWS FETCH FIRST 20 ROWS ONLY
Here is an example of the expected results manually created from the view function.
[
(int) 0 => object(App\Model\Entity\Service) {
'callID' => (int) 361893,
'subject' => 'Printer jam',
'customer' => 'C202044',
'custmrName' => 'Some Company',
'contctCode' => (int) 986,
'manufSN' => '',
'internalSN' => '16J151800861',
'createDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-17 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'createTime' => (int) 1612,
'closeDate' => null,
'closeTime' => null,
'DocNum' => (int) 316939,
'Series' => (int) 30,
'schedules' => [
(int) 0 => object(App\Model\Entity\Schedule) {
'SrcvCallID' => (int) 361893,
'Line' => (int) 1,
'Technician' => (int) 243,
'StartDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-18 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'StartTime' => (int) 800,
'EndDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-18 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'EndTime' => (int) 900,
'U_NB_ChkInDate' => null,
'U_NB_ChkInTime' => null,
'U_NB_ChkOutDate' => null,
'U_NB_ChkOutTime' => null,
'SignData' => null,
'Duration' => (float) 1,
'DurType' => 'H',
'Sched_Closed' => 'N',
'U_NB_TechRate' => (float) 50,
'U_NB_FollowUp' => null,
'[new]' => false,
'[accessible]' => [],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[hasErrors]' => false,
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Schedules'
},
(int) 1 => object(App\Model\Entity\Schedule) {
'SrcvCallID' => (int) 361893,
'Line' => (int) 2,
'Technician' => (int) 243,
'StartDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-17 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'StartTime' => (int) 1600,
'EndDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-17 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'EndTime' => (int) 1630,
'U_NB_ChkInDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-17 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'U_NB_ChkInTime' => (int) 1600,
'U_NB_ChkOutDate' => object(Cake\I18n\FrozenTime) {
'time' => '2020-03-17 00:00:00.000000-04:00',
'timezone' => 'America/New_York',
'fixedNowTime' => false
},
'U_NB_ChkOutTime' => (int) 1630,
'SignData' => null,
'Duration' => (float) 30,
'DurType' => 'M',
'Sched_Closed' => 'N',
'U_NB_TechRate' => (float) 150,
'U_NB_FollowUp' => 'Y',
'[new]' => false,
'[accessible]' => [],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[hasErrors]' => false,
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Schedules'
}
],
'[new]' => false,
'[accessible]' => [],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[hasErrors]' => false,
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Services'
},
]
Again, thanks for any assistance.

Related

BelongsToMany associated record won't save in join table

I have 2 tables "Descriptions" and "Phases", associated BelongsToMany using a join Table "DescriptionsPhases", as per Cakephp naming conventions. I've been struggling with this for the best part of a day now: I just cannot get the controller to save the id's of description and phase into my join table, which is basically just 3 columns, id / description_id / phase_id. Possibly more may follow for metadata.
Here is my code structure:
In Descriptions Table:
$this->belongsToMany('Phases', [
'foreignKey' => 'description_id',
'targetForeignKey' => 'phase_id',
'through' => 'DescriptionsPhases',
'saveStrategy' => 'append'
]);
In Phases Table:
$this->belongsToMany('Descriptions', [
'foreignKey' => 'phase_id',
'targetForeignKey' => 'description_id',
'through' => 'DescriptionsPhases'
]);
In DescriptionsPhases Table:
$this->belongsTo('Descriptions', [
'foreignKey' => 'description_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Phases', [
'foreignKey' => 'phases_id',
'joinType' => 'INNER'
]);
In Entities of all 3 of the above (for now):
protected $_accessible = [
'*' => true
];
In Descriptions Controller, add() method. The id for $descriptions->phases is hardcoded for now, just to reduce complexity:
public function add() {
$description = $this->Descriptions->newEntity([
'associated' => ['Phases']]);
if ($this->request->is('post')) {
// additional data to be saved in new description
$description->user_id = $this->Auth->user('id');
$description->designvariant = 666;
// Hardcoded integer TO BE SAVED in associated model (via join table)
$description->phases = [['id' => 5]];
$patch = $this->Descriptions->patchEntity($description, $this->request->getData(), [
'associated' => ['Phases']]);
debug($patch);
$save = $this->Descriptions->save($patch);
debug($save);
if ($save) {
$this->Flash->success(__('The description has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The description could not be saved. Please, try again.'));
}
// pass arrays for dropdown menus
$this->set('trades', ($this->Descriptions->Trades->listLabels()));
$this->set('elements', ($this->Descriptions->Elements->listLabels()));
$this->set('allocations', ($this->Descriptions->Allocations->listLabels()));
$this->set(compact('description'));
$this->render();
}
I have debugged both after patchEntity and saveEntity, and get this data structure.
debug($patch);
object(App\Model\Entity\Description) {
'associated' => [
(int) 0 => 'Phases'
],
'user_id' => (int) 1,
'designvariant' => (int) 666,
'phases' => [
(int) 0 => [
'id' => (int) 5
]
],
'element_id' => 'A',
'shorttext' => 'title',
'longtext' => 'text',
'trade_id' => '0',
'allocation_id' => 'BMST',
'[new]' => true,
'[accessible]' => [
'trade_id' => true,
'element_id' => true,
'allocation_id' => true,
'shorttext' => true,
'longtext' => true,
'designvariant_id' => true,
'user_id' => true,
'created' => true,
'modified' => true,
'trade' => true,
'element' => true,
'allocation' => true,
'phase' => true,
'phases' => true,
'designvariant' => true,
'user' => true,
'amounts' => true,
'costs' => true,
'descriptions_phases' => true,
'descriptions_phase' => true,
'*' => true
],
'[dirty]' => [
'associated' => true,
'user_id' => true,
'designvariant' => true,
'phases' => true,
'element_id' => true,
'shorttext' => true,
'longtext' => true,
'trade_id' => true,
'allocation_id' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Descriptions'
}
debug($save);
object(App\Model\Entity\Description) {
'associated' => [
(int) 0 => 'Phases'
],
'user_id' => (int) 1,
'designvariant' => (int) 666,
'phases' => [
(int) 0 => [
'id' => (int) 5
]
],
'element_id' => 'A',
'shorttext' => 'title',
'longtext' => 'text',
'trade_id' => '0',
'allocation_id' => 'BMST',
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2019-01-11T10:04:32+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2019-01-11T10:04:32+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'id' => (int) 133,
'[new]' => false,
'[accessible]' => [
'trade_id' => true,
'element_id' => true,
'allocation_id' => true,
'shorttext' => true,
'longtext' => true,
'designvariant_id' => true,
'user_id' => true,
'created' => true,
'modified' => true,
'trade' => true,
'element' => true,
'allocation' => true,
'phase' => true,
'phases' => true,
'designvariant' => true,
'user' => true,
'amounts' => true,
'costs' => true,
'descriptions_phases' => true,
'descriptions_phase' => true,
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Descriptions'
}
The record is saved smoothly into the Descriptions Table, but it just won't save any associated data.
Am I missing something obvious?
Any help is gratefully received. Also, if you need more info, plz ask as it's my 1st post here :-)
Only entities are being saved, ie phases must hold an array of entities, not nested arrays. The nested arrays are what you'd pass to newEntity()/patchEntity(), where they would be converted into entities accordingly.
$description->phases = [
$this->Descriptions->Phases->get(5)
];
See also
Cookbook > Database Access & ORM > Saving Data > Saving With Associations
Cookbook > Database Access & ORM > Saving Data > Saving Entities > Saving Associations

Cakephp 3 - belongsToMany with _joinData

I try to realize a bolongsToMany association with additional data in the join table. The join table has columns for the foreign keys and an additional column 'context'
articlesController:
$article = $this->Articles->patchEntity($article, $this->request->getData());
debug($article);die;
and in a Plugin-Behavior:
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
$data['Categories.Categories'] = ['id' => '1', '_joinData' => ['context' => 'Tag']];
debug($data);
}
I expect the context beeing saved in the join table, but it doesn't. The debgger says:
/plugins/Categories/src/Model/Behavior/CategorizeableBehavior.php (line 37)
object(ArrayObject) {
name => 'dfa'
description => 'er'
Categories.Categories => [
'id' => '1',
'_joinData' => [
'context' => 'Tag'
]
]
}
/src/Controller/ArticlesController.php (line 56)
object(App\Model\Entity\Article) {
'name' => 'dfa',
'description' => 'er',
'[new]' => true,
'[accessible]' => [
'name' => true,
'description' => true,
'created' => true,
'modified' => true
],
'[dirty]' => [
'name' => true,
'description' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Articles'
}
Where's my category and context. What's wrong with my code?
If you want to attach join data it needs to be in a nested array.
$data['Categories.Categories'] = [
['id' => '1', '_joinData' => ['context' => 'Tag']]
];
It has to be a nested array so that you could attached multiple records (if you wanted too).

How to save additional data to join Table if joined entities are created new

i am trying to save additional data to a belongsToMany join Table.
I followed the instructions here
But they are just in case the entities already exist, because an id is used it seems. But my entities should be new created and additional join data should be saved.
My save data looks like this. Everything is persisted fine, except the additional field 'type_keys'
(int) 0 => object(Cloud\Model\Entity\MediaObject) {
'media_object_type_id' => 'image',
'title' => '1482842705_1_749145',
'relative_path' => '/optional_images/1/1/',
'extension' => 'jpg',
'size' => (int) 142683,
'original_title' => 'logo.jpg',
'_joinData' => [
'type_key' => 'optional_image_1'
],
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'media_object_type_id' => true,
'title' => true,
'relative_path' => true,
'extension' => true,
'size' => true,
'original_title' => true,
'_joinData' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Cloud.MediaObjects'
}
Unfortunately just the joind ids in the join table are saved, but not the joinData field 'type_keys'
I would be happy if someone can give me a clue.
On further testing i found out that the join data gets overwritten when saving.
For your information: I am setting the media object join data in the beforeSave callback.
object(Cloud\Model\Entity\Touchpoint) {
'title' => 'test',
'user_id' => (int) 1,
'tp_image' => [
'name' => '',
'type' => '',
'tmp_name' => '',
'error' => (int) 4,
'size' => (int) 0
],
'optional_image_1' => [
'name' => 'logo.jpg',
'type' => 'image/jpeg',
'tmp_name' => '/tmp/phpMeTwuQ',
'error' => (int) 0,
'size' => (int) 142683
],
'created' => object(Cake\I18n\Time) {
'time' => '2016-12-27T13:19:03+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2016-12-27T13:19:03+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'brand_id' => (int) 1,
'media_objects' => [
(int) 0 => object(Cloud\Model\Entity\MediaObject) {
'media_object_type_id' => 'image',
'title' => '1482844743_1_988232',
'relative_path' => '/optional_images/1/1/',
'extension' => 'jpg',
'size' => (int) 142683,
'original_title' => 'logo.jpg',
'_joinData' => object(Cake\ORM\Entity) {
'touchpoint_id' => (int) 8,
'media_object_id' => (int) 8,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'MediaObjectsTouchpoints'
},
'created' => object(Cake\I18n\Time) {
'time' => '2016-12-27T13:19:03+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2016-12-27T13:19:03+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'id' => (int) 8,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [
'_joinData' => [
'type_key' => 'optional_image_1'
]
],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Cloud.MediaObjects'
}
],
'id' => (int) 8,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Cloud.Touchpoints'
}
So i know it gets overwritten, but i am not sure how to do this the right way?
Ok i found the solution now.
Since i saw an entity is created as _joinData i created an entity myself and did set the property in the entity myself, this way the _joinData does not get replaced, but just enriched with the ids.
$joinTable = TableRegistry::get('MediaObjectsTouchpoints');
$newMediaObject->_joinData = $joinTable->newEntity();
$newMediaObject->_joinData->type_key = 'something';
$entity->media_objects[] = $newMediaObject;

How to retrieve only those records that have at least one associated record?

The data should show only the tenancies that contains data. In normal query normally I do this be right join.
I know contain() is left join on default but can contain be right join at all?
My query:
$properties = $this->find()
->select([
'Property.id', 'Property.company_id', 'Property.address1', 'Property.postcode',
])
->contain([
'Tenancies' => function($q) {
return $q
->select([
'Tenancies.id','Tenancies.property_id','Tenancies.created',
'Tenancies.stage', 'Tenancies.landlord_offer_sent', 'Tenancies.offer_letter_contract_id',
])
->contain([
'Tenants' => function($q) {
return $q
->select([
'Tenants.id', 'Tenants.stage', 'Tenants.tenancy_id', 'Tenants.holding_fee',
])
->where([
'active = 1',
]);
}
])
->where([
'Tenancies.active = 1',
]);
}
])
->where(['Property.active = 1', $conditions])
->toArray();
e.g. I need to get read of nodes that tenancies that are null 'tenancies' => [], and it should show only node 1.
Prints=>
(int) 0 => object(App\Model\Entity\Property) {
'id' => (int) 95,
'company_id' => (int) 3,
'address1' => '40 Arthur Street',
'postcode' => 'LE11 3AY',
'tenancies' => [],
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Property'
},
(int) 1 => object(App\Model\Entity\Property) {
'id' => (int) 102,
'company_id' => (int) 3,
'address1' => 'Grace Dieu Court',
'postcode' => 'LE11 4QH',
'tenancies' => [
(int) 0 => object(App\Model\Entity\Tenancy) {
'id' => (int) 16,
'property_id' => (int) 102,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2015-05-08T09:30:41+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'stage' => (int) 6,
'landlord_offer_sent' => false,
'offer_letter_contract_id' => (int) 37,
'tenants' => [
(int) 0 => object(Cake\ORM\Entity) {
'id' => (int) 16,
'stage' => (int) 7,
'tenancy_id' => (int) 16,
'holding_fee' => (float) 50,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Tenants'
},
(int) 1 => object(Cake\ORM\Entity) {
...
...
...
I have tried inner join 'joinType' => 'INNER' but no luck:
class TenancyTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('tenancy');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Properties', [
'foreignKey' => 'property_id',
'className' => 'property',
'joinType' => 'INNER'
]);
Not possible via containments for hasMany associations
This cannot be done with containments, as hasMany ones are being retrieved via a separate query. Instead you'll have to add a proper join yourself, an INNER join (more portable than a RIGHT join) could do it if you want to restrict the results to only those that have associated Tenancies.
A simple Query::innerJoinWith() without conditions, and some grouping to avoid duplicates
$properties = $this
->find()
// ...
->innerJoinWith('Tenancies')
->group('Property.id');
... and you're done, this will add a join like
INNER JOIN tenancies Tenancies ON Property.id = Tenancies.property_id
innerJoinWith() works for all other types of associations too
For belongsTo and hasOne associations however, it could be done via containments, as for these types of associations, all data is being retrieved in the same query. To filter by those that have an associated record, just change the join type to INNER, like
$this
->find()
->contain([
'BelongsToAssociated' => [
'joinType' => 'INNER'
]
]);
See also
Cookbook > Database Access & ORM > Query Builder > Filtering by Associated Data
Cookbook > Database Access & ORM > Query Builder > Using innerJoinWith
This worked when I simply changed contains to matching.
It seems to be possible, see Filtering By Associated Data

How to print _matchingData object value in cakephp 3

I have printed object and trying to print Tenats.stage
<?php foreach ($tenancies as $tenancy): ?>
<td><?= debug($tenancy); ?></td>
Print this =>
object(App\Model\Entity\Tenancy) {
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2016-03-18T15:57:40+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'tenants' => [],
'property' => object(Cake\ORM\Entity) {
'id' => (int) 4110,
'address1' => '119 Alan Moss Road',
'postcode' => 'le115ly',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Properties'
},
'_matchingData' => [
'Tenants' => object(Cake\ORM\Entity) {
'stage' => (int) 2,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Tenants'
}
],
I need to 'stage' value
Any help please
That's how to print _matchingData
<td><?= h($tenancy->_matchingData['Tenants']->stage); ?></td>
But if you specify the main parents field id (Tenancy.id) automaticaly your data will look much much better. for example my parent model is "Tenancy" Now I am getting Tenant.id and Tenancy.id and Property.id :
$tenancies = $this
->find()
->select([
'Tenancy.id', 'Tenancy.created', 'Tenancy.stage',
'Properties.id', 'Properties.address1', 'Properties.postcode',
'Tenants.stage',
])
->contain('Properties', function(\Cake\ORM\Query $query) {
return $query->where([
'Properties.active' => 1
]);
})
->contain([
'Tenants'
])
->matching('Tenants', function(\Cake\ORM\Query $query) {
return $query->where([
'Tenants.active' => 1
]);
})
->where([
'Tenancy.active' => 1,
$conditions
])
->order([
'Tenancy.created' => 'DESC',
'Tenants.tenancy_id'
]);
return $tenancies;
}
It prints the array with deep associations which is cool and I can get my tenants property like this:
<td><?= h($tenancy->tenants->stage); ?></td>
Prints=>
0 => object(App\Model\Entity\Tenancy) {
'id' => (int) 3923,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2016-03-19T13:12:32+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'stage' => (int) 2,
'tenants' => [
(int) 0 => object(Cake\ORM\Entity) {
'id' => (int) 8903,
'user_id' => (int) 15318,
'tenancy_id' => (int) 3923,
'needs_guarantor' => true,
'guarantor_id' => null,
'holding_fee' => (float) 50,
Result: Make sure always you write your query properly so you access your data nice and tidy.

Resources