Deep associations in select query in cakephp3.7 - cakephp

I'm new to cakephp3. I'm unable to join associated tables data. I have
Employees Table.
EmployeeLogins Table
EmployeeCategories Table
Relation:
Employees table hasMany EmployeeLogins
And EmployeeLogins belognsTo EmployeeCategories
I'm using contain but not getting EmployeeCategories data.
The result i want is like
In single result set
Output :
array(
'Employees' => array(
'id' => 123,
),
'EmployeeLogin' => array(
[0] => array(
'employee_id' => 123,
'last_login' => '2019-01-01',
'category_id' => 3,
'EmployeeCategory' => array(
'id' => 3,
'category_name' => 'contract'
)
)
)
)
EmployeesTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('employees');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('EmployeeLogins', [
'foreignKey' => 'user_id',
'dependent' => true
]);
}
EmployeeLoginsTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('employee_logins');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->belongsTo('Categories', [
'foreignKey' => 'category_id',
]);
}
EmployeeCategoriesTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('employee_catgories');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
}
Code in Controller
$Employees = TableRegistry::get('Employees');
$EmployeeLogins = TableRegistry::get('EmployeeLogins');
$EmployeeCategories = TableRegistry::get('EmployeeCategories');
$Employees->hasMany('EmployeeLogins', [
'className' => 'EmployeeLogins',
'foreignKey' => 'employee_id',
'propertyName' => 'employee_logins'
]);
$Employees->EmployeeLogins->belongsTo('EmployeeCategories', [
'className' => 'EmployeeCategories',
'conditions' => [
'EmployeeLogins.category_id' => 'EmployeeCategories.id'
],
'propertyName' => 'employee_categores',
]);
$result = $Employees
->find()
->where([
'Employees.user_id)' => $user_id,
])
->contain([
'EmployeeLogins' => [
'EmployeeCategories'
]
])
->all();

You need to change your contain section like below
$result = $Employees
->find()
->where([
'Employees.user_id)' => $user_id,
])
->contain([
'EmployeeLogins', 'EmployeeLogins.EmployeeCategories'
])
->all();

Related

Belongs to Many: save custom field

I need help with saving/updating belongsToMany data with custom field.
I have a tables acl, managers, groups, sections.
I need to save/update data in acl table from sections/edit or sections/add
acl table
id|target_id|target_type|section_id
I need set target_id data 'user' or 'group' depending entities.
edit.ctp
echo $this->Form->control('managers._ids', ['class' => 'multiple', 'multiple' => true]);
echo $this->Form->control('groups._ids', ['class' => 'multiple', 'multiple' => true]);
SectionsTable.php
$this->belongsToMany('Managers', [
'joinTable' => 'acl',
'through' => 'Acl',
'foreignKey' => 'section_id',
'targetForeignKey' => 'target_id',
'conditions' => [
'target_type' => 'user'
]
]);
$this->belongsToMany('Groups', [
'joinTable' => 'acl',
'through' => 'Acl',
'foreignKey' => 'section_id',
'targetForeignKey' => 'target_id',
'conditions' => [
'target_type' => 'group'
]
]);
AclTable.php
$this->belongsTo('Managers', [
'foreignKey' => 'target_id',
'conditions' => [
'target_type' => 'user'
]
]);
$this->belongsTo('Groups', [
'foreignKey' => 'target_id',
'conditions' => [
'target_type' => 'group'
]
]);
I tried to use _joinData, but nothing work
before and after patchEntity()
foreach ($section->groups as &$group) {
$group->_joinData = ['target_type' => 'group'];
}
whithout save()
foreach ($this->request->data['managers']['_ids'] as $id) {
$manager = $this->Sections->Managers->get($id);
$manager->_joinData = new Entity(['target_type' => 'user'], ['markNew' => true]);
$this->Sections->Managers->link($section, [$manager]);
}
$this->request->data has a follow structure:
[
'controller' => '*',
'action' => '*',
'title' => '*',
'managers' => [
'_ids' => [
'0' => '*',
'1' => '*',
'2' => '*',
]
],
'groups' => [
'_ids' => [
'0' => '*',
'1' => '*',
'2' => '*',
]
]
]
Alltimes error: Cannot insert row, some of the primary key values are missing. Got (3, 114, ), expecting (target_id, section_id, target_type)
Now working with following code
$section = $this->Sections->patchEntity($section, $this->request->data, ['associated' => ['Managers', 'Groups']]);
foreach ($section->managers as &$manager) {
$manager->_joinData = new Entity([
'target_id' => $manager->id,
'target_type' => 'user',
'section_id' => $section->id
], ['markNew' => true]);
}
foreach ($section->groups as &$group) {
$group->_joinData = new Entity([
'target_id' => $group->id,
'target_type' => 'group',
'section_id' => $section->id
], ['markNew' => true]);
}
if ($this->Sections->save($section, ['associated' => ['Managers', 'Groups']])) {
$this->Flash->success(__('The section has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The section could not be saved. Please, try again.'));
But, maybe can do it don't listing all fields?
Also I need to create/add new records to acl table when adding new section
But section_id unavailable until save(). Can I save/add acl records by associations
Updated
AclTAble.php
$this->setPrimaryKey(['target_type', 'target_id', 'section_id']);
.
.
.
$this->belongsTo('Managers', [
'foreignKey' => 'target_id',
'conditions' => [
'target_type' => 'user'
]
]);
$this->belongsTo('Groups', [
'foreignKey' => 'target_id',
'conditions' => [
'target_type' => 'group'
]
]);
With primaryKey id, adding and editing entities working, but editing makes dublicates, it's are not perfect for me. And in documentations use composite primaryKey, so i use ['target_id', 'target_type', 'section_id'], with it cake don't make dublicated, but aggain show me error: Cannot insert row, some of the primary key values are missing. Got (8, 197, ), expecting (target_id, section_id, target_type)

HasOne associations table is not saving

Hi I'm trying to save data across 2 tables (Entries and Conditions) at the same time. Entries has a column called foreign_key which is a relation to the Conditions primary id.
I was testing the saving portion and one of my tables (Conditions) saved but my other table (Entries) did not.
Entry.php
protected $_accessible = [
'metadata' => true,
'type' => true,
'foreign_key' => true,
'created' => true,
'modified' => true,
'condition' => true
];
Condition.php
protected $_accessible = [
'user_id' => true,
'data' => true,
'created' => true,
'modified' => true,
'user' => true,
'entry' => true
];
ConditionsTable.php (where I declared the association)
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('conditions');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->hasOne('Entries', [
'foreignKey' => 'foreign_key',
'bindingKey' => 'id',
'propertyName' => 'entries',
'joinType' => 'INNER'
]);
}
DataControlellr.php (where I'm testing the saving)
$this->loadModel('Conditions');
$data = [
'user_id' => 'b26ee991-a27c-441b-a78b-dd2a1dbf5164',
'data' => json_encode(['test'=>1,'test2' => 2]),
'entry' =>[
'meta' => json_encode(['test'=>1,'test2' => 2]),
'type' => 'conditions'
]
];
$entity = $this->Conditions->newEntity($data,['associated' => 'Entries']);
//dd($entity);
dd($this->Conditions->save($entity));
exit;
}
So again entries table is not saving a row and conditions is, I believe I'm using the right association (has one) but maybe that not the right logic? Much help is appreciated.
The key in your data and your association name need to match. You probably want to change your association to this:
$this->hasOne('Entries', [
'foreignKey' => 'foreign_key',
'bindingKey' => 'id',
'joinType' => 'INNER'
]);
assuming that the class for the fhir_entries table is called EntriesTable, not FhirEntriesTable.
Alternately, leave the association named as it is but change the propertyName to entry (hasOne properties should be singular). Or, change it to fhir_entry and the key in the data array from entry to fhir_entry to match.

record not found on contain model in CakePHP 3

I am using CakePHP 3.4
I am retrieving information of user like
$user = $this->Users->get($id, [
'contain' => [
'UserAddresses.States.Countries' => ['conditions' => [
'UserAddresses.deleted' => false]],
],
]);
Here, UserAddresses have user_id column and state_id column and States table have country_id column.
If there is no associated user_address in table, then I gives
record not found error
while removing States.Countries from contain works fine even when record does not exists in UserAddresses
Edit 2 : Relationships
UsersTable.php
$this->hasOne('UserAddresses', [
'foreignKey' => 'user_id'
]);
UserAddressesTable.php
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('States', [
'foreignKey' => 'state_id',
'joinType' => 'INNER'
]);
StatesTable.php
$this->belongsTo('Countries', [
'foreignKey' => 'country_id',
'joinType' => 'INNER'
]);
$this->hasMany('UserAddresses', [
'foreignKey' => 'state_id'
]);
CountriesTable.php
$this->hasMany('States', [
'foreignKey' => 'country_id'
]);
As Kilian Schuster said, your condition is being applied to Countries. I changed it, try the code bellow:
$user = $this->Users->get($id, [
'contain' => [
'UserAddresses' => [
'conditions' => [
'UserAddresses.deleted' => false
]
],
'UserAddresses.States.Countries'
]
]);
Edit: The record not found error could be caused by the Inner join type for Countries in States model if there is no address related to the got user. Change the join type to Left if the relationship is optional.
With this syntax, you are actually trying to apply the conditions to the "Countries" model, and not to the "UserAddresses" model.

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

Cakephp - HABTM relationship lost after calling method

The Issue
I have two methods I need to call in my getContacts function. These methods are as follows:
Method 1:
$authenticated = $this->authenticateUserInternal($inputData['auth']);
Method 2:
$result = $this->Consumer->find('first', array( 'conditions' => array('Consumer.id' => 81)));
When I invoke these methods one after the other my resultset in $result does not have data from related tables. It seems like Method 1 is killing the relationship or affecting my results somehow, but it's a completely un-related function in the AppController.
The Code
The the Consumer model has a HABTM relationship to my Users model through a join table consumers_users.
Consumer Model
public $hasAndBelongsToMany = array(
'User' => array( 'className' => 'User', 'joinTable' => 'consumers_users', 'foreignKey' => 'consumer_id', 'associationForeignKey' => 'user_id', ));
User Model
public $hasAndBelongsToMany = array(
'Consumer' => array(
'className' => 'Consumer',
'joinTable' => 'consumers_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'consumer_id',
));
ConsumersUsers Model
public $belongsTo = array(
'User' => array( 'className' => 'User', 'foreignKey' => 'user_id', ), 'Consumer' => array( 'className' => 'Consumer', 'foreignKey' => 'consumer_id', ), );
My normal result
"Consumer": {
"id": "81",
"fullname": "consumer2",
"email": "consumer2#cici.mailgun.org",
"password": "111111",
"dateregistered": "2013-08-18",
"audio": "0"
}, "User": [
{
"id": "179",
"fullname": "leesa",
"email": "lda#hotmail.com",
"password": "111111",
"imageurl": "http://bridgefiles.s3.amazonaws.com/2013-07-18-144613_IMG_0773.jpeg",
"thumb_url": "http://bridgefiles.s3.amazonaws.com/2013-07-18-144613_IMG_0773.jpeg",
"messagecount": "2",
"dateregistered": "2013-07-29",
"phone1": null,
"phone2": null,
"videoaddress": null,
"ConsumersUser": {
"id": "17",
"consumer_id": "81",
"status": "accepted",
"user_id": "179"
}
},
The Actual Result
"Consumer": {
"id": "81",
"fullname": "consumer2",
"email": "consumer2#cici.mailgun.org",
"password": "111111",
"dateregistered": "2013-08-18",
"audio": "0"
}
You are selecting only the table consumer, I think that the right way is to load the join table and insert recursive 1 to retrieve both table Consumer and User
try this:
$this->loadModel('ConsumersUsers');
$result = $this->ConsumersUsers->find('first', array( 'conditions' => array('Consumer.id' => 81), 'recursive' => 1));

Resources