I would like to create a junction table tbl_guid_cost_centre that gets taken care of without me manually saving it to the database. I tried adding this to my relations:
'costCentre' => [
self::HAS_ONE,
'CostCentre',
'guid_to',
'foreignKey' => 'guid',
'tbl_guid_cost_centre(guid_to, cost_center_id)',
"order" => "id desc"],
so that my when saving the costCentre, a row is created for it in my tbl_guid_cost_centre. However I'm getting the error:
Property "CHasOneRelation.0" is not defined.
Any suggestion?
You can have your junction table with the keyword through in your relations:
public function relations() {
'guidCostCentre' => [
self::HAS_ONE,
'GuidCostCentre',
['guid_to' => 'guid']
],
'costCentre' => [
self::HAS_ONE,
'CostCentre',
'cost_centre_id',
'through' => 'guidCostCentre'
]
}
You're defining HAS_ONE relation in a wrong way. The first three elements of relation configuration array should be: relation type, related model name and foreign keys definition. All further elements should be indexed by keys related to relation properties. 'tbl_guid_cost_centre(guid_to, cost_center_id)', probably generates this error, because it does not have a key, so it is treaded as a value for 0 property. You didn't share any details, so it is hard to guess what you want to achieve, but you should start from something like this:
'costCentre' => [
self::HAS_ONE,
'CostCentre',
'guid_to',
'order' => 'id desc',
],
And add additional settings at the end array with correct key.
Some examples and explanation you can find in the documentation: https://www.yiiframework.com/doc/guide/1.1/en/database.arr#declaring-relationship
Related
I have this model:
Proforma
->hasMany('ItemProformas', ['foreignKey' => 'proforma_id']);
->belongsTo('Customers', ['foreignKey' => 'customer_id']);
->belongsTo('ProformaStates', ['foreignKey' => 'proforma_state_id']);
->hasMany('Invoices', ['foreignKey' => 'proforma_id']);
ItemProformas
->belongsTo('Proformas', ['foreignKey' => 'proforma_id', 'joinType' => 'INNER']);
->belongsTo('ItemDeliveryNotes', ['foreignKey' => 'item_delivery_note_id']);
ItemDeliveryNotes
->belongsTo('DeliveryNotes', ['foreignKey' => 'delivery_note_id', 'joinType' => 'INNER']);
->belongsTo('ItemOrders', ['foreignKey' => 'item_order_id']);
->belongsTo('ItemOrdersTypes', ['foreignKey' => 'item_orders_type_id']);
->belongsTo('Products', ['foreignKey' => 'product_id']);
Each ItemProforma may have one ItemDeliveryNotes, otherwise the foreign key will be null. Here my paginate call:
$this->paginate = [
'contain' => [
'Customers',
'ProformaStates',
'ItemProformas' => ['ItemDeliveryNotes' => ['DeliveryNotes']]
]
];
With this model, I get all the itemProforma that have item_delivery_note_id set. Instead I'm interesed to get them all, even if item_delivery_note_id is null.
I'm not sure if belongsTo is correct here (I mean in ItemProformas definition). But hasOne implies it has one associated row, not may have one.
What is the correct syntax to retrieve all itemProformas even if they don't have any ItemDeliveryNote associated? But if they have, I need to retrieve the ItemDeliveryNote object as well.
The association type depends on your schema. If the foreign key is in the source table, then it's belongsTo, if the foreign key is in the target table, then it's hasOne.
Whether a related record must exist primarily depends on the schema too, not on the type of association. If the foreign key is nullable, then the related record is optional. If and how you implement enforcing that constraint on application level is a different story.
That being said, ItemDeliveryNotes and DeliveryNotes are both belongsTo that will use joins by default, so both associations will be joined into the same query, and since you've configured the DeliveryNotes association to use an INNER join, it will exclude rows where no DeliveryNotes exist, which of course is also the case when no ItemDeliveryNotes exist.
Assuming your schema is modeled correctly/properly, you could for example change your association config to use a LEFT join by default in case applicable, or you could change the configuration for the containment on a per query basis (being it manually, or by using a custom finder):
$this->paginate = [
'contain' => [
'Customers',
'ProformaStates',
'ItemProformas' => [
'ItemDeliveryNotes' => [
'DeliveryNotes' => [
'joinType' => \Cake\Database\Query::JOIN_TYPE_LEFT,
],
],
],
],
];
Changing the fetching strategy for ItemDeliveryNotes could work too (though it might be quite taxing depending on the amount of records), ie using the select strategy instead of the join strategy, then the associated ItemDeliveryNotes records are being retrieved in a separate query, and thus won't affect retrieval of ItemProformas:
$this->paginate = [
'contain' => [
'Customers',
'ProformaStates',
'ItemProformas' => [
'ItemDeliveryNotes' => [
'strategy' => \Cake\ORM\Association::STRATEGY_SELECT,
'DeliveryNotes',
],
],
],
];
consider i have 2 models that have the pivot relationship many to many between them . now when i want to insert the pivot table how can i achieve it currently i am doing this :
DB::table('model1_model2')
->insert([
'something' => $something,
'something2' => $something2,
]);
and i kinda feel that its not right and i have do save it with some relation ship or sync !! any idea how to do this ?
EDIT:
Added relationship
public function accommodationRoom()
{
return $this->belongsToMany(AccommodationRoom::class)->withPivot('guest_first_name','guest_last_name','guest_cell_phone','guest_nationality_id');
}
As mentioned in the Inserting & Updating Related Models - Many To Many Relationships documentation:
When attaching a relationship to a model, you may also pass an array of additional data to be inserted into the intermediate table:
$model1->accommodationRoom()->attach($accommodationRoomId, [
'something' => $something,
'something2' => $something2,
]);
If you're attaching multiple relations then you would would pass a multidimensional array with the keys are the id of the relation and the values are the arrays of additional data:
$model1->accommodationRoom()->attach( [
1 => ['something' => $something, 'something2' => $something2,],
3 => ['something' => 'something else', 'something2' => $something2,],
]);
The same is true for the sync() method as well.
Problem description
I'm trying to configure a CakePHP 3.7 API to save associated data in a child-first manner. The entities - for the sake of example, lets call them Users and Persons - and their relationships are as follows:
UsersTable.php
...
$this->belongsTo('Persons', [
'foreignKey' => 'person_id',
'joinType' => 'LEFT',
'className' => 'MyPlugin.Persons',
]);
...
PersonsTable.php
$this->hasOne('Users', [
'foreignKey' => 'person_id',
'className' => 'MyPlugin.Users'
]);
In their respective entities, they each have one another's property visibility set to true. What I'm trying to do is POST to the /users/ route (UsersController.php) and have it also save the Persons object included. The payload is as such:
{
"username": "foo",
"password": "bar",
"persons": {
"dob": "1982-07-03",
}
}
The relevant part of the saving method is below, from UsersController.php:
if ($this->request->is('post') && !empty($this->request->getData())) {
$data = $this->request->getData();
$newEntity = $this->Users->newEntity($data, ['associated' => 'Persons']);
$savedEntity = $this->Users->save($newEntity);
...
The error
This produces the following SQL error.
PDOException: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column 'person_id' violates not-null constraint
DETAIL: Failing row contains (1, null, foo, bar)
I understand this is because Cake is attempting to save to Users without having a person_id to satisfy the foreign key constraint. It's not possible to reverse this FK relationship in my application domain as we desire leftward one-to-many relationship (User -> 1 Person).
I suspect sending an id in the persons object of the JSON payload will allow this to save correctly. However, for various reasons, this isn't possible at runtime. For example, this is how it's shown in the "Saving Data" CakePHP Book page...
$data = [
'title' => 'First Post',
'user' => [
'id' => 1,
'username' => 'mark'
]
];
...
$article = $articles->newEntity($data, [
'associated' => ['Users']
]);
$articles->save($article);
I know the following would also likely work as suggested by xPfqHZ for a similar issue, as Persons can save to Users, but it feels less suitable as compared to what I'm trying to do and feels as if there is a way via the associations on Users.
if ($this->request->is('post') && !empty($this->request->getData())) {
$data = $this->request->getData();
$newEntity = $this->Users->Persons->newEntity($data, ['associated' => 'Persons']);
$savedEntity = $this->Users->Persons->save($newEntity);
...
Workings
Now I believe this used to be possible in CakePHP 2.X, as stated in this answer by ndm on a similar question where a person is attempting to save the belongsTo associated entity and it's parent hasOne entity in one request via the belongsTo entity.
That's the expected behavior, saveAssociated() is not meant to save only the associated records, it will save the main record as well, so you should use saveAssociated() only, no need to manually set the foreign key, etc, CakePHP will do that automatically.
Controller
public function create() {
if ($this->request->is('post') && !empty($this->request->data)):
$this->CandidatesProblemReport->create();
if ($this->CandidatesProblemReport->saveAssociated($this->request->data)):
// ...
endif;
endif;
}
However, I'm not able to find or use the saveAssociated() method upon the Cake\ORM\Table object which the Users entity inherits from, in the documentation. Calling it produces a method not found error. This method only appears to exist on the Cake\ORM\Association object as detailed in the documentation. Unless I'm missing the obvious, is there a way to use this or is it used internally by BelongsTo() and its sibling methods?
Logging / Dumping entity
Using Cake\Log\Log::error($newEntity); or die(var_dump($newEntity)); shows the Users data of the payload hydrated into an object, but I don't see the Persons object attached (see below).
object(MyPlugin\Model\Entity\User)[299]
public 'username' => string 'foo' (length=3)
public 'password' => string 'bar' (length=3)
public '[new]' => boolean true
public '[accessible]' =>
array (size=5)
'*' => boolean false
'person_id' => boolean true
'username' => boolean true
'password' => boolean true
'person' => boolean true
public '[dirty]' =>
array (size=2)
'username' => boolean true
'password' => boolean true
public '[original]' =>
array (size=0)
empty
public '[virtual]' =>
array (size=0)
empty
public '[hasErrors]' => boolean false
public '[errors]' =>
array (size=0)
empty
public '[invalid]' =>
array (size=0)
empty
public '[repository]' => string 'MyPlugin.Users' (length=17)
Attempting to \Cake\Log\Log::error($savedEntity); shows nothing in the log file.
save() associations arguments
Another solution I considered was using the $options['associated] of save() as shown in the documentation (extract below). With this set to true as below, the error still occurred.
save( Cake\Datasource\EntityInterface $entity , array $options [] )
... associated: If true it will save 1st level associated entities as they are found in the passed $entity whenever the property defined for the association is marked as dirty. If an array, it will be interpreted as the list of associations to be saved. It is possible to provide different options for saving on associated table objects using this key by making the custom options the array value. If false no associated records will be saved. (default: true) ...
UsersController.php:
if ($this->request->is('post') && !empty($this->request->getData())) {
$data = $this->request->getData();
$newEntity = $this->Users->newEntity($data, ['associated' => 'Persons']);
$savedEntity = $this->Users->save($newEntity, ['associated' => true]);
...
Summary
Without going through the PersonsController.php and utilising its hasOne relationship, I'm not having much luck getting my Users and Persons data to save through the UsersController.php.
If I've missed any important information, or you have questions/need more, please ask! I might have missed something obvious, but I'd appreciate any suggestions/solutions possible.
As #ndm identified, the error lay in the posted data. As per the "Saving Data: Saving BelongsTo Associations" page of the documentation:
When saving belongsTo associations, the ORM expects a single nested entity named with the singular, underscored version of the association name.
The posted key persons should have been person. Equally, if the entity were named PersonSnapshots, the relevant key in the payload hydrated into the entities would need to have been person_snapshot.
i'd like to set hasOne and hasMany to same model,in a part of my code i need only 1 result, but in other part i need all result (Objects from type Client that will return for a table in my site):
$this->hasOne('Vendas')
->setForeignKey('id_cliente')
->setBindingKey('id')
;
$this->hasMany('Vendas')
->setForeignKey('id_cliente')
->setBidingKey('id');
This is possible, or i made a mistake?
Read the manual https://book.cakephp.org/3.0/en/orm/associations.html. Read the whole page carefully.
className: the class name of the table being associated to the current model. If you’re defining a ‘User hasOne Address’ relationship, the className key should equal ‘Addresses’.
conditions: an array of find() compatible conditions such as ['Addresses.primary' => true]
Define the class name and conditions you need for your assocs.
$this->hasOne('Foo', [
'className' => 'Foo',
'conditions' => [/* whatever you need*/]
]);
$this->hasMany('Bar', [
'className' => 'Foo',
'conditions' => [/* whatever you need*/]
]);
If the relationship is 1-* you should define the relationship as hasMany(). Then you write a query for one result and a query for multiple results
I know the Question title is a bit murky, but here's what I'm trying to do:
I'm retrieving a list of groups that a user belongs to from a third party api. In some cases, the user will be an 'admin' for a group and other times, just a 'member'.
Specifics aside, I'm calling a method on my api class from my controller that hits the api, retrieves the user's groups, decides if they are an 'admin' or not, then returns an array of arrays with each group's information including a 'role' key that denotes whether or not they are an 'admin'. So my returned array looks something like this:
[
0 => [
'unique_id' => 1243657,
'name' => 'Group1',
'city' => 'Bluesville',
'state' => 'IN',
'role' => 'admin'
],
1 => [
'unique_id' => 4324567,
'name' => 'Group2',
'city' => 'New Curtsbourough',
'state' => 'WI',
'role' => 'member'
],
2=> [
'unique_id' => 87463652,
'name' => 'Group3',
'city' => 'Samsonite',
'state' => 'MN',
'role' => 'member'
]
]
Now, I need to take those groups and store them in the database, which I'm doing by checking first that the group doesn't exist in the database, then adding it if needed. Of course, I'm leaving off the role, as it is only relevant to the current user.
Next, I need to connect the current user to these groups that were just retrieved. I have a pivot table set up that currently holds the user_id and group_id.
The question is, how to best handle this. Before I decided that I needed to know whether or not a member was an 'admin' or not, I simply had my 'createGroups' method return an array of primary keys to me, then passed that array to a call to
$user->groups()->sync($array_of_ids);
However, with the added 'role' information, it's not as cut and dry.
Basically, at this point in the lifecycle, I have access to an array of groups that contains a field 'role'. My thinking says to add a 'role' field to the pivot table, which would then contain 'user_id', 'group_id' and 'role'. This means I'll not only need the $groups array with the retrieved groups, but the ids of those groups as they pertain to my database.
I could make something work, but I'm afraid it would be extremely messy and inefficient.
Thoughts anyone??
Ok, as happens many times on Stackoverflow, I've come to a solution for my own question. I'm posting so that in the off-chance someone stumbles upon my question needing to do something similar, they can at least see how one person handled it.
According to the Laravel docs, if you want to sync relationships with an added column, you need to call sync in the following way:
$user->groups()->sync([
1 => ['role' => 'admin'],
2 => ['role' => 'member'],
3 => ['role' => 'member']
]);
So before I could sync, I needed an array that resembled the array that is being passed to 'sync'.
Since I had an array of 'groups' that included a field called 'role' for each group, I created a 'createGroups' method that basically looped over the $groups array and called the 'insertGetId' method that Laravel provides. This method persists the object to the database and returns the primary key of the created record. For my 'createGroups' method, I did the following:
public function createGroups($groups)
{
$added = array();
foreach($groups as $group){
$id = $this->createGroup($group);
$added[$id] = ['role' => $group['role']];
}
return $added;
}
So as I'm inserting 'groups' into the database, I'm building up the array that is needed by the 'sync' method. Since the 'createGroup' method uses Laravel's 'insertGetId' method, it returns the primary key for that group. Then I use that id as the key to the array. After all groups are inserted, my 'added' array that is returned to my controller, looks like this:
[
1 => ['role' => 'admin'],
2 => ['role' => 'member'],
3 => ['role' => 'member']
]
which is exactly what the 'sync' method needs to do it's thing.
Happy coding!