CakePHP contain() method sees belongsToMany over belongsTo of same table - php

So I have the following (simplified) model
+------------------+
| project_statuses |
+------------------+
+---------------------+ +----| id |
| projects | | | name |
+---------------------+ | +------------------+
| id | | +---------+
| name | | | clients |
| project_statuses_id |----+ +---------+
| client_id |---------| id |
+---------------------+ | name |
| +---------+
+------------------+ |
| clients_projects |------+
+------------------+
| id |
| client_id |
| project_id |
+------------------+
where a project belongs to many clients and a client can have many projects, but only one client (Projects.client_id) can take the responsibility for a project. The project status is here just for comparision.
So the associations in my ProjectsTable.php, ClientsTabe.php and ProjectStatusesTable.php look like this
// In ProjectsTable.php
$this->belongsTo('ProjectStatuses', [
'foreignKey' => 'project_status_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Clients', [
'foreignKey' => 'project_id',
'targetForeignKey' => 'client_id',
'joinTable' => 'clients_projects'
]);
// In ClientsTabe.php
$this->hasMany('Projects', [
'foreignKey' => 'client_id'
]);
$this->belongsToMany('Projects', [
'foreignKey' => 'client_id',
'targetForeignKey' => 'project_id',
'joinTable' => 'clients_projects'
]);
// In ProjectStatusesTable.php
$this->hasMany('Projects', [
'foreignKey' => 'project_status_id'
]);
Now in my projects/index page I'd like to have a table showing the project id, name, status and responsible. So I thought in something like this
// In ProjectsController.php
$this->Projects->find()->select(['Projects.id','Projects.name'])
->contain(['ProjectStatuses' => [
'fields' => [
'ProjectStatuses.name',
]]])
->contain(['Clients' => [
'fields' => [
'Clients.name',
]]]);
But only ProjectStatuses.name is fetched, for Clients.name it throws the error You are required to select the "ClientsProjects.project_id" field(s) telling me that is looking through the belongsToMany association rather than the belongsTo one.
In fact, if I just write ->contain('Clients') instead of specifying the Clients.name field it send the following queries
SELECT
Projects.id AS `Projects__id`,
Projects.name AS `Projects__name`,
ProjectStatuses.name AS `ProjectStatuses__name`
FROM
projects Projects
INNER JOIN project_statuses ProjectStatuses ON ProjectStatuses.id = (Projects.project_status_id)
SELECT
ClientsProjects.id AS `Clients_CJoin__id`,
ClientsProjects.client_id AS `Clients_CJoin__client_id`,
ClientsProjects.project_id AS `Clients_CJoin__project_id`,
Clients.id AS `Clients__id`,
Clients.name AS `Clients__name`,
FROM
clients Clients
INNER JOIN clients_projects ClientsProjects ON Clients.id = (ClientsProjects.client_id)
WHERE
ClientsProjects.project_id in (4)
How can I tell the query object to get the Clients.name through Projects.client_id just as I get ProjectStatuses.name through Projects.project_statuses_id instead of passing by the ClientProjectstable?

In your Projects table, you have created two associations for Clients, and vice versa. That doesn't work (as The Highlander said, "There can be only one"), and is likely the source of all your problems.
You'll need to change one of those associations in each table. Maybe a Project should belongsToMany Clients but only belongTo one ResponsibleClient, and a Client should belongsToMany Projects and hasMany ReponsibleProjects?

Related

Using select2_from_ajax with a JSON datasource

I am new to Laravel and to Backpack for Laravel so please bear with me. I am trying to create a client registration form that features a "State" field which is populated dynamically depending on the value selected for the "Country" field.
I am following the instructions provided by Backpack's author here: https://backpackforlaravel.com/docs/3.5/crud-fields#select2_from_ajax
Both states and countries come from this dataset: https://github.com/antonioribeiro/countries. They are returned as collections but they are not read from the DB.
Table schema for Clients (simplified)
+-------------------+---------+--------+
| Field | Type | Length |
+-------------------+---------+--------+
| uuid | CHAR | 36 |
| name | VARCHAR | 128 |
| city | VARCHAR | 128 |
| state | VARCHAR | 64 |
| country_iso_cca2 | VARCHAR | 2 |
+-------------------|---------+--------+
The good part
The "Country" field works just fine. It fetches data from the JSON dataset at creation and reads/writes info from or to the DB on update/save:
// ClientCrudController.php
$countries = new Countries();
$allCountriesCodes = $countries->all()->pluck('name.common', 'cca2')->toArray();
$this->crud->addField([
'name' => 'country_iso_cca2',
'label' => 'Country',
'type' => 'select2_from_array',
'options' => $allCountriesCodes,
'allows_null' => false,
'default' => 'US',
]);
The bad (incomplete) part
// ClientCrudController.php
$this->crud->addField([
'name' => 'state',
'label' => 'State',
'type' => 'select2_from_ajax',
'entity' => 'foobar', // the method that defines the relationship in your Model
'attribute' => 'name',
'data_source' => url('api/states'),
'placeholder' => 'Select a country first...',
'dependencies' => ['country_iso_cca2'],
]);
Calling /admin/client/create will result in an error ("Call to undefined method App\Models\Client::foobar").
I understand that the error is raised because there is no model defined for States and hence no relationship. My problem is, I do not understand what the implementation is supposed to look like in a case like this where the two select fields do not represent separate entities at an ORM level.
Is it possible to implement this kind of dependency in a "backpack-native" way, without resorting to creating a custom field type?

BelongsToMany associations in CakePHP 3, how to keep relations and save associations in a secondary field?

I need help to create a belongsToMany relation between 2 tables. I need this relation to be permanent and the "state of the relation" to be stored in an additional field in the relation table.
Here goes some background. I'm buildind a website for a restaurant.
This restaurant has a few cards (Brasserie, Gastronomique...):
cartes
id | name | description
Every card has many sections (Starters, Salad, Meat, Fish...). Every section may happen in multiple cards.
cartes_rubriques
id | name | description | comment
Cards and Sections have a belongsToMany relation thanks to a table
cartes_cartes_rubriques
id | carte_id | rubrique_id| order | description | comment | active (boolean)
I need this relation to be permanent. The relation may be temporarly removed as some sections may be seasonal but I need to keep the order, description and comment to be saved.
I've tried 'saveStrategy' => 'append' but this only adds new records to cartes_cartes_rubriques.
CartesTable.php
$this->belongsToMany('Rubriques', [
'foreignKey' => 'carte_id',
'targetForeignKey' => 'rubrique_id',
'through' => 'cartes_rubriques'
]);
CartesRubriquesTable.php
$this->belongsToMany('Cartes', [
'foreignKey' => 'rubrique_id',
'targetForeignKey' => 'carte_id',
'through' => 'cartes_rubriques'
]);
CartesCartesRubriquesTable.php
$this->belongsTo('Cartes', [
'foreignKey' => 'carte_id',
'joinType' => 'INNER'
]);
$this->belongsTo('CartesRubriques', [
'foreignKey' => 'cartes_rubrique_id',
'joinType' => 'INNER'
]);
My strategy would ideally be to save theses relations and toggle cartes_cartes_rubriques.active.
Does this seem to be a good / possible strategy?
How can I make the toggeling on cartes_cartes_rubriques.active?
Do you have a better option?
Thanks for your help.
Antonio

cakephp 3 paginate sort custom field

i try to sort "hasMany" associate
this is my code :
Advisor Modal
$this->hasMany('AdvisorNotes', [
'foreignKey' => 'id',
'sort' => [
'created' => 'DESC',
],
'joinType' => 'LEFT',
]);
Advisor Controller
public function index() {
$this->paginate = [
'contain' => [
'Branch' => [ 'BranchCity', 'Bank' ],
'AdvisorLevel',
'AdvisorRelation',
'UsersAgent',
'AdvisorNotes'
],
'sortWhitelist' => [
'BranchCity.city_name',
'Branch.name',
'Advisor.name',
'AdvisorRelation.relation',
'AdvisorLevel.level',
'Advisor.telephone',
'UsersAgent.name',
'AdvisorNotes.created',
'Bank.name',
]
];
$advisors = $this->paginate( $this->Advisor );
AdvisorNotes table
id | advisor_id | agent_id | type_id | note | created
Advisor table
id | name | image | date_of_birth | important_date | branch_id | telephone | fax | cellphone | email | description | level_id | relation_id | agent_id | agent_admin_id | created
i try to make field in the Advisor index that show the last AdvisorNote and be able to sort it.
as you can see AdvisorNotes as advisor_id in table but Advisor dont have any id of the AdvisorNotes.
thanks.

Cakephp 3 - Save associated belongsToMany (joinTable)

i have a question about saving a new entity with an association which is a belongsToMany relation. Just a quick introduction. I have something like a settings table and a setting_values table. The user is connected to the settings by a joinTable called users_settings. So in summary:
settings:
id | name
setting_values:
id | setting_id | name | value |
users_settings:
id | user_id | setting_id | setting_value_id
Now before the user is added I want to patch the entity of the user with all settings and the first setting_value for each setting. So that users_settings has now all settings connected to the user with the first value. But I can't get the patch or the newEntity to be work. All models are baked so this should be fine. Here's my code
$settings = $this->Settings->find('all', [
'contain' => [
'SettingValues'
]
]);
$settingsData = [];
foreach ($settings as $setting) {
$settingsData[] = [
'setting_id' => $setting->id,
'setting_value_id' => $setting->setting_values[0]->id,
];
}
$data = [
'users_settings' => $settingsData
];
$user = $this->Users->patchEntity($user, $data, [
'associated' => [
'Settings.UsersSettings'
]
]);
This is the result in the $user entity. As you can see nothing is corretly marshaled:
users_settings => [
(int) 0 => [
setting_id => (int) 1,
setting_value_id => (int) 1
],
(int) 1 => [
setting_id => (int) 2,
setting_value_id => (int) 5
]
]
Can someone give me an advice on this. Thanks
That's not how belongsToMany association data should be structured, you have to supply the target, not the join table. Additional join table data can be supplied via the special _joinData key, ie the data should look like:
$data = [
'settings' => [
[
'id' => 1,
'_joinData' => [
'setting_value_id' => 1
]
],
[
'id' => 2,
'_joinData' => [
'setting_value_id' => 5
]
]
]
];
That would populate the join table like:
+----+---------+------------+------------------+
| id | user_id | setting_id | setting_value_id |
+----+---------+------------+------------------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 5 |
+----+---------+------------+------------------+
And if you (need to) use the associated option, then make sure to specify the _joinData key too:
$user = $this->Users->patchEntity($user, $data, [
'associated' => [
'Settings._joinData'
]
]);
See also
Cookbook > Database Access & ORM > Saving Data > Saving BelongsToMany Associations
Cookbook > Database Access & ORM > Saving Data > Saving Data To The Join Table

CakePHP - One HABTM With Two Foreign Keys

I've been trying to save two foreign keys to the same row of a join table with no success. My Client model has two HABTM association with both User and Group models, a user can have many clients and create clients under many different groups.
The UserClient join table looks like this:
+----+---------+----------+-----------+
| id | user_id | group_id | client_id |
+----+---------+----------+-----------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 2 |
| 3 | 3 | 4 | 3 |
+----+---------+---------+------------+
Client model:
public $hasAndBelongsToMany = array(
'User' => array(
'className' => 'User',
'joinTable' => 'UserClient',
'foreignKey' => 'client_id',
'associationForeignKey' => 'user_id'
),
'Group' => array(
'className' => 'Group',
'joinTable' => 'UserClient',
'foreignKey' => 'client_id',
'associationForeignKey' => 'group_id'
)
);
Client view:
<?php echo $this->Form->create('Client'); ?>
<fieldset>
<legend><?php echo __('Add Client'); ?></legend>
<?php
echo $this->Form->input('first_name');
echo $this->Form->input('last_name');
echo $this->Form->input('email');
echo $this->Form->input('phone');
echo $this->Form->input('Client', array('multiple'=> 'false', 'options' => $group_ids));
echo $this->Form->hidden('User', array('value' => $user_id));
?>
</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
This works to a point, however instead of saving user_id, group_id and client_id to the same row in UserClient table, it create two separate rows for each HABTM, user_id, client_id get saved to one row and group_id, client_id get saved to another.
Is there any way to define multiple associationForeignKey within the same HABTM association so user_id, group_id and client_id get saved to the same row when creating a new client?
When you have to store additional fields in a HABTM relation, it is sometimes easier to use a join model. In your case this would mean creating a model for your UserClient table and use hasMany relations instead of HABTM.
Then of course you'll have to manage the save manually, but you'll have much more control of what must be done. You can find a few words about this in the Cake documentation.

Categories