CakePHP 3 - saving associated data with correct id - mysql

I am fairly new to CakePHP, and am working on an event calendar. The events can be single day events or multiple days, so I created an events table and a dates table. When I save an event, the dates are saved in the Dates table as expected BUT the event_id field is always saved as 0 instead of the actual event id.
I think the relationships between the Events and Dates table is set properly.
This is what I have for the DatesTable:
$this->table('dates');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Events', [
'foreignKey' => 'event_id'
]);
This is my EventsTable:
$this->table('events');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Dates', [
'foreignKey' => 'event_id',
'joinType' => 'INNER'
]);
This is the code in my EventsController (WetKit is one of our plugins):
$event = $this->Events->newEntity();
if ($this->request->is('post')) {
$event = $this->Events->patchEntity($event, $this->request->data, [
'associated' => 'dates'
]);
if ($this->Events->save($event)) {
$this->Flash->success(__('The form has been saved.'));
} else
$this->WetKit->flashError(__('The form could not be saved because error(s) were found.'), $event);
}
How can I get the event id to save in the Dates table?

The association on the EventsTable side is wrong, it should be either a hasMany (1:n) association, or a hasOne (1:1) association. Given that you're talking about multiple days, I'd assume that you're looking for the former.
See also
Cookbook > Database Access & ORM > Associations - Linking Tables Together

Related

CakePHP3 - creating multiple associations between same two records

I have a many-to-many relationship between Invoices and Items. An Item can appear multiple times in the same Invoice, but with different amounts (e.g. when the same service is used several times during one month).
I was hoping I could create this association by including the following in the 'items' element of the invoice when saving (see https://book.cakephp.org/3/en/orm/saving-data.html#saving-belongstomany-associations):
Array
(
[0] => Array
(
[id] => 1
[_joinData] => Array
(
[amount] => 5338.29
)
)
[1] => Array
(
[id] => 1
[_joinData] => Array
(
[amount] => 5988.53
)
)
[2] => Array
(
[id] => 1
[_joinData] => Array
(
[amount] => 6023.40
)
)
)
In the example above, the result I'm hoping for is that three rows are created in the join table invoices_items. The invoice is saved correctly, but only one row is created in the join table.
One both associations I tried setting saveStrategy to append (I wasn't sure what this does), but this didn't help:
$this->belongsToMany('Invoices', [
'saveStrategy' => 'append'
]);
Is it possible to achieve this behaviour out of the box, or do I need to create something more custom, like a new model to specifically keep track of these relationships? Please let me know if more code would help to clarify what I'm trying to achieve.
Thanks!
The answer seems to be that no, this type of mass assignment isn't possible.
The solution I arrived at is to loop through each item I want to associate with the freshly saved invoice and call link() for each. I don't know what the performance hit is for this but for my purposes it works as this operation in my case happens relatively rarely.
// Build an array with an element per item
...
$itemsJoinData[] = [
'item' => $item,
'_joinData' => [
'amount' => $amount
]
];
...
Once the invoice is successfully saved I attach the items with their respective amount.
// Join up the invoice with the items
foreach($itemsJoinData as $itemToJoin) {
$itemToJoin['item']->_joinData = new Entity(['amount' => $itemToJoin['_joinData']['amount']], ['markNew' => true]);
$this->Invoices->Items->link( $invoice, [$itemToJoin['item']] );
}

Yii2 GridView display only related records

I have this database structure:
I need the user to get only those reservations in ReservationsSeachModel that are in the warehouse belonging to the user. Model user_has_warehouse has a function getUserHasWarehouse($user_id, $warehouse_id) to check for relations. I am able to get the relations when viewing single, deleting and updating records in controllers. But I cannot make the SearchModel to return reservations that belong to current user.
I cannot depend on Reservation->user_id, because that value is not mandatory and reservations can be inserted publicly with no user_id. How to make the SearchModel work properly so it only shows reservations belonging to the warehouse where user has the warehouse?
ReservationsSearchModel:
$warehouse_id = User_Has_Warehouse::find()->select(['warehouse_id'])->where(['user_id' => Yii::$app->user->id]);
$freezer_type_id = Freezer_type::find()->select(['id'])->where(['warehouse_id' => $warehouse_id]);
$reservation_id = Reservation_freezer_type::find()->select(['reservation_id'])->where(['freezer_type_id'=>$freezer_type_id]);
...
$query->andFilterWhere([
'id' => $reservation_id,
'customer_id' => $this->customer_id,
'user_id' => $this->user_id,
]);
...
But the following did not work for me. What am I doing wrong?
Managed to solve it.
$h = User_Has_Warehouse::find()->select(['warehouse_id'])->where(['user_id' => Yii::$app->user->id]);
$rt = Freezer_type::find()->select(['id'])->where(['warehouse_id' => $h]);
$res = Reservation_freezer_type::find()->select(['reservation_id'])->where(['freezer_type_id' => $rt]);
// grid filtering conditions
$query->andFilterWhere(['id' => $res]);

CakePHP 3.x - Saving associated data and form inputs of those tables

I have 3 tables:
Bookings
Payments
Sessions
In my form which is part of the Bookings table, while I have several attributes exclusive to Payments (eg. Amount, Depositpaid, Status), only one has a form input - everything else is Sessions related:
echo $this->Form->hidden('payments.amount',['type'=>'number', 'value'=>'0', 'step'=>'0.01', 'class'=>'currency form-control']);
In the BookingsController, I have the following:
$booking = $this->Bookings->newEntity([
'associated' => ['Payments', 'Sessions']
]]);
if ($this->request->is('post')) {
$data = $this->request->data;
$booking = $this->Bookings->patchEntity($booking, $data, [
'associated' => ['Payments', 'Sessions']
]);
...
$save = $this->Bookings->save($booking);
...
Upon clicking submit on the form, data in both Bookings and Sessions is saved. In the post data, there are two arrays: Session array and Payments array. In the variables data inside the Booking array, there is an Associated array with two elements: 0 Payments, 1 Sessions.
However, in the SQL log, there is no Insert Into for Payments table. When doing a debug of $booking (the patchEntity one), Payments is an empty array as follows:
'payments' => [],
I also did a debug of $save that comes after patchEntity, and obtained the same result for 'payments'.
I checked my Payments table model and the corresponding structure in MySQL when it came to not nulls and validation.
Below is the Payments model (PaymentsTable) regarding validation:
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->allowEmpty('paymentmethod','create');
$validator
->decimal('amount')
->requirePresence('amount', 'create')
->notEmpty('amount');
$validator
->requirePresence('status', 'create')
->notEmpty('status');
$validator
->integer('depositpaid')
->allowEmpty('depositpaid', 'create');
$validator
->allowEmpty('uniqid', 'create');
return $validator;
}
In my MySQL database, I set attribute 'status' to automatically be set to 'notpaid' upon the creation of a new data entry, and also set attribute 'amount' to have a default value of '0.00'. The only not null fields are 'id', 'booking_id' (foreign key that links to the Booking table's id), 'amount' and 'status'.
So I guess my question is: Is the reason I'm not getting this associated table saved because I did not put in form inputs (hidden or otherwise) for the remainder of the Payments attributes or is there something I'm missing?
Update:
Okay I added a 0 to the form input (payments.0.amount), and this time the submit did not succeed. Upon debugging patchEntity - 'debug($booking)' once more, I find this error in the payments array:
'[errors]' => [
'status' => [
'_required' => 'This field is required'
]
This is even though I stated status to be set to automatically put in a default string. Do I need to do something like $this->request->data['status'] in the controller? And if so, how does it vary from an attribute for non-associated data (eg. $this->request->data['attribute'] is used for non-associated data)?
I've already tried:
$this->request->data['status'] = 'notpaid';
$this->request->data['payments']['status'] = 'notpaid';
$this->request->data['payments'][0]['status'] = 'notpaid';
$this->request->data['payments']['0']['status'] = 'notpaid';
All without success and receiving the same error in the patchEntity debug. The method that does work is adding in another form input for 'status' (the attribute that says it is required), but although I've solved my issue, I'm still left wondering how to input associated data from the controller.
I fixed it by just adding in another hidden form input, although I would still like to know how to input association data through the controller instead of the view.

CakePHP- querying a HABTM table and then accessing data

CAKEPHP question
I am querying my HABTM table successfully and returning the id of every student with the given group_id. This is my code for this.
$students = $this->GroupsStudents->find('list', array('conditions' => array('group_id' => $id)));
It works, no problem. I need to somehow use this info (namely the student's id), which is stored in $students, to query my students table and extract student's names based on their id.
If someone could give me some insight on this, that would be greatly appreciated.
if i'm understanding you right. as you can see from this if you have the id you can easily get the students name even though i'm not sure why you would do this and not just foreach the name.
foreach ($students as $id => $name) {
echo $students[$id]; // produces name
}
In Student model define relation with GroupStudent model as shown below:
var $hasMany = array(
'GroupStudent' => array(
'className' => 'GroupStudent',
'foreignKey' => 'student_id'
)
);
Then your write your query as
$students = $this->Student->find('all',
array(
'conditions' => array('GroupStudent.group_id' => $id)
'fields'=>array('Student.name','Student.id','GroupStudent.group_id'))
);
Note: Make sure your controller has $uses=>array('Student','GroupStudent'); defined!
and your are using plural names for model GroupStudents so correct them if possible

Yii Query optimization MySQL

I am not very good with DB queries. And with Yii it's more complicated, since I am not very used to it.
I need to optimize a simple query
$userCalendar = UserCalendar::model()->findByAttributes(array('user_id'=>$user->id));
$unplannedEvents = CalendarEvent::model()->findAllByAttributes(array('calendar_id'=> $userCalendar->calendar_id,'planned'=>0));
CalendarEvent table, i.e the second table from which I need records does not have an user_id but a calendar_id from which I could get user_id from UserCalendar, i.e. the first table hence I created a UserCalendar object which is not a very good way as far as I understand.
Q1. What could I do to make it into one.
Q2. Yii does this all internally but I want to know what query it built to try it seperately in MySQL(phpMyAdmin), is there a way to do that?
Thanks.
Q1: You need to have the relation between UserCalendar and CalendarEvent defined in both of your active record models (in the method "relations").
Based on your comments, it seems like you have the Calendar model that has CalendarEvent models and UserCalendar models.
Lets assume your relations in Calendar are:
relations() {
return array(
'userCalendar' => array(self::HAS_MANY, 'UserCalendar', 'calendar_id'),
'calendarEvent' => array(self::HAS_MANY, 'CalendarEvent', 'calendar_id'),
}
In CalendarEvent:
relations() {
return array( 'calendar' => array(self::BELONGS_TO, 'Calendar', 'calendar_id'), );
}
And in UserCalendar:
relations() {
return array( 'calendar' => array(self::BELONGS_TO, 'Calendar', 'calendar_id'), );
}
So to make the link between UserCalendar and CalendarEvent you'll need to use Calendar
$criteria = new CDbCriteria;
$criteria->with = array(
"calendarEvent"=>array('condition'=>'planned = 0'),
"userCalendar"=>array('condition'=> 'user_id =' . $user->id),
);
$calendar = Calendar::model()->find($criteria);
and $calendar->calendarEvent will return an array of calendarEvent belonging to the user
Q2: you can enable web logging so all the db request (and others stuffs) will appear at the end of your page:
Logging in Yii (see CWebLogging)
In your application configuration put
'components'=>array(
......
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CWebLogRoute',
),
),
),
),