Saving Additional Data to a Joint Table in CakePHP 3.0 - php

I am trying to save some data into a Joint Table using CakePHP. This is the part of the application that I would like to fix - it is a normal BelongsToMany association with additional columns:
Model > Entity:
/* Durations */
class Duration extends Entity {
protected $_accessible = [
'duration' => true,
'cost' => true,
];
}
/* Commercials */
class Commercial extends Entity {
protected $_accessible = [
'info' => true,
'commercial_durations' => true,
];
}
/* CommercialDurations */
class CommercialDuration extends Entity {
protected $_accessible = [
'duration_id' => true,
'commercial_id' => true,
'quantity' => true,
'duration' => true,
'commercial' => true,
];
}
Model > Table:
class DurationsTable extends Table {
public function initialize(array $config)
{
$this->table('durations');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsToMany('Commercials', [
'through' => 'CommercialDurations',
]);
}
}
class CommercialsTable extends Table
{
public function initialize(array $config){
$this->table('commercials');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsToMany('Durations', [
'through' => 'CommercialDurations'
]);
$this->hasMany('CommercialDurations', [
'foreignKey' => 'commercial_id'
]);
}
}
class CommercialDurationsTable extends Table {
public function initialize(array $config)
{
$this->table('commercial_durations');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Durations', [
'foreignKey' => 'duration_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Commercials', [
'foreignKey' => 'commercial_id',
'joinType' => 'INNER'
]);
}
}
Now, I created a new View where I want people to be able to choose one Duration, type the quantity and add that value to the database. I am using the following code:
<?php
echo $this->Form->create($commercial);
echo $this->Form->input('durations._duration', ['options' => $durations]);
echo $this->Form->input('durations._joinData.quantity');
echo $this->Form->submit(__('Next'), ['class' => 'button small right', 'escape' => false]);
echo $this->Form->end()
?>
The problem with this form is that the durations select is not showing the 'duration' field from the Durations table, but instead is showing all the fields from that table (one per row) as JSON
<option value="0">{ "id": 1, "duration": "30 sec", "cost": 450 }</option>
Once I submit the form I can't save this information into the Commercials object or CommercialDurations. This is what I get from the $this->request->data object:
[
'durations' => [
'_duration' => '2',
'_joinData' => [
'quantity' => '2'
]
]
]
The output of debug((string)$commercial) before I start the form is:
/src/Template/Commercials/features.ctp (line 22)
'{
"id": 2,
"info": "AAAAAA ",
"created": "2015-04-16T21:48:48+0000",
"updated": null,
"durations": [],
}'
How can I display the data correctly on the form?
How can I retrieve and save this data?
Thanks!!

I don't get your Models relations, since according to your post a duration belongs to a commercial and a commercial belongs to a duration.
For the matter of explaining you how the request should be sent, and how the form looks like let's assume for this example that your models are like these:
Your commercial has a commercial duration and this commercial duration belongs to a duration
So your models would look like these:
Commercial has many commercial duration.
Commercial duration belongs to commercial.
Commercial duration belongs to duration.
Duration has many commercial duration.
Your add function should be like this one (assuming you are not saving a new duration, just the commercial and the commercial duration)
$commercial = $this->Commercials->newEntity($this->request->data,[
'associated' => ['Commercialdurations']
]);
if ($this->Commercials->save($commercial)) {
$success = true;
}else{
$success = false;
}
The request data should look like this:
{
"commercialdurations":{
"Commercialduration":{
"duration_id":"1",
"quantity":"1",
"duration":"1"
}
},
"info":"something"
}
Basically you are sending the data of a new Commercial (info) and the commercial durations associated to this ( you could send multiple commercial durations).
For your form to display the duration basically you have to serialize this information un the controller, go to your add action and add this. (You could use anyway you want to retrieve the data, all that matters is that you send it back to the view)
$durations = $this->Commercials->Commercialdurations->Durations->find('list', ['limit' => 200]);
$this->set(compact('commercial',durations'));
$this->set('_serialize', ['commercial']);
Then in your form you can use the data
echo $this->Form->input('duration_id', ['options' => $durations]);
So your form would look something like this:
echo $this->Form->input('info');
echo $this->Form->input('commercials.Commercial.duration_id', ['options' => $durations]);
echo $this->Form->input('commercials.Commercial.quantity');
echo $this->Form->input('commercials.Commercial.duration');
Basically you want to send a request with all the levels of associated data.
See this other question for guidence about saving associated data:
Cake PhP 3 Saving associated data in multiples levels
To see more about how to build a form:
http://book.cakephp.org/3.0/en/views/helpers/form.html#creating-inputs-for-associated-data

Related

CakePHP 3: saving hasOne association ($_accessible not set)

I have read several Stack Overflow posts and the documentation pages about saving associated data in CakePHP 3, but I can't get my code to work. When creating a new Organisation, I also want to save the data of the new account (NewAccount) that belongs to that Organisation.
Below is a reproducible part of my code. The validation of the Organisations model are executed and if passed, the data is saved. The NewAccounts data does not get saved, and is not even being validated. I have already checked the naming conventions in all of these files, but I can't find anything that might be a problem anymore.
// Model/Table/OrganisationsTable.php
class OrganisationsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->hasOne('NewAccounts');
}
}
// Model/Table/NewAccountsTable.php
class NewAccountsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->belongsTo('Organisations', [
'foreignKey' => 'organisation_id'
]);
}
}
// Template/Organisations/admin_add.ctp
echo $this->Form->create($organisation);
echo $this->Form->control('new_account.email', [
'label' => __('Email address'),
'class' => 'form-control',
]);
echo $this->Form->control('new_account.name', [
'label' => __('Name'),
'class' => 'form-control',
]);
echo $this->Form->control('name', [
'label' => __('Organisation name'),
'div' => 'form-group',
'class' => 'form-control',
]);
// Controller/OrganisationsController.php
class OrganisationsController extends AppController
{
public function adminAdd() {
$organisation = $this->Organisations->newEntity();
if($this->request->is('post')) {
$this->Organisations->patchEntity($organisation, $this->request->getData(), [
'associated' => ['NewAccounts']
]);
if ($this->Organisations->save($organisation)) {
$id = $organisation->id;
debug("Success!");
}
else {
debug("Error");
}
}
$this->set(compact('organisation'));
}
}
CakePHP documentation I have referenced:
Saving Data
Form
Associations - Linking Tables Together
Validating Data
Entities
I forgot to make $organisation->new_account accessible:
// Model/Entity/Organisation.php
class Organisation extends Entity
{
protected $_accessible = [
// ...
'new_account' => true,
];
}
By doing this, the field is marked as to be safely assigned.
Entities: Mass Assignment (book) and EntityTrait::$_accessible (API docs)

how to use laravel eloquent to get and display data from other tables

I was using these codes in my controller to get all the data from my 2 tables and it works fine
$All = Customers::with('order')->paginate(10);
return response()->json([
'code' => 0,
'success' => true,
'data' => $All
], 200);
Here is how I define the relationship between these 2 tables
class Customers extends Model
{
public function order()
{
return $this->hasMany(Orders::class, 'customer_id', 'id');
}
}
class Orders extends Model
{
public function customers()
{
return $this->belongsTo(Customers::class, 'customer_id', 'id');
}
}
Now my desire output is to hide the order id, order timestamps and change the customer_id to customer's name (the customer's name is not in my orders db table).
I'm using 'data' => DataResource::collection($All) in my controller and this is my DataResource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'order' => $this->order
];
}
and of course the output is same with the image above.
My database structure:
orders table:
customer table:
Can anyone help me with that?
The answer is simple and basically a copy of the official documentation. You simply need to wrap your orders in an OrderResource as well.
// DataResource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'order' => OrderResource::collection($this->order)
];
}
// OrderResource
public function toArray($request)
{
return [
'items' => $this->items,
'quantity' => $this->quantity
];
}
I don't really understand why you would want to include the customer_name in your orders when it is already present on the customers object one hierarchy above. But if you really want to add it, you should be able to do so with: 'customer_name' => $this->customers->name.
As a side note: you really should be more consistent with your naming. Why is the resource called DataResource when it is about Customers? Why is your model called Customers in plural form rather than Customer in singular, which is the convention (and more logical if you consider that one model represents one customer). Why is your belongsTo relation called customers() in plural when it returns one customer, while your hasMany relation is called order whereas it returns one or more orders?

How to store the request data into 3 different tables?

I have a form that allows an authenticated user create a new conference.
This form will request the user information and some of this information introduced by the user should be stored in the Conference table, some in the "Conference_Category" table and some in the "RegistrationType" table.
So when the user submit the form "dd($request->all());" shows:
array:20 [▼
"_token" => "DbDKu3ryk4NdxKLr3aO132iHrE0O5b0XqfENk1"
"conference_name" => "Conference Name"
"conference_categories" => "1"
"conference_description" => "description"
"conference_creator_name" => "John"
"conference_creator_email" => "email#testemail.email"
"conference_creator_description" => "description"
"registration_type_name" => "registration type name"
"registration_type_description" => "registration type desc"
"registration_type_capacity" => "100"
]
DOUBT
My doubt is how to properly store this request information. Because its necessary to store the request data into 3 different tables to create a new conference:
In the "Conference" table is necessary to store: the conference_name, conference_description, conference_creator_id, conference_creator_name,conference_creator_email, conference_creator_description
In the "conference_category" table is necessary to store: the conference categories id's and the conference id
In the "RegistrationType" table is necessary to store:registration_type_name, registration_type_description and registration_type_capacity
I have a ConferenceController and I have the logic to create a new conference in the store() method of the ConferenceController. But for now I just have the part to store the request information on the Conference table.
Do you know how to also store the categories introduced by the user and the registration type in the respective tables (conference_category and RegistrationType)?
ConferenceController create method:
<?php
namespace App\Http\Controllers;
use App\Category;
use App\Conference;
use Illuminate\Http\Request;
class ConferenceController extends Controller
{
public function store(Request $request)
{
dd($request->all());
$this->validate($request, [
'conference_name' => 'required|max:255',
'conference_categories' => 'required|array|between:1,3',
'conference_startDate' => 'required',
'conference_creator_name' => 'required',
'conference_creator_email' => 'required|email|unique:users',
'conference_creator_description' => '',
'conference_registration_type_name' => 'required',
'confenrece_registration_type_description' => '',
'conference_registration_type_capacity' => 'required',
]);
$conference = Conference::create([
'name' => $request->conference_name,
'description' => $request->conference_description,
'startDate' => $request->conference_startDate,
'conference_creator_id' => Auth::user()->id,
'conference_creator_name' => $request->conference_creator_name,
'conference_creator_email' => $request->conference_creator_email,
'conference_creator_description' => $request->conference_creator_description
]);
}
}
The relationships relevants for this questions are:
Many to many between Conference and Categories (A conference can have many categories, so there is a third table conference_category)
one to many between User and Conference (A user can create many conferences)
one to many between Conference and RegistrationType (A conference can have many registration types)
Conference Model
class Conference extends Model
{
protected $fillable = [
'name', 'description', 'startDate', '...'
];
public function categories(){
return $this->belongsToMany('App\Category');
}
public function conference_creator(){
return $this->belongsTo('App\User', 'conference_creator_id');
}
public function registrationTypes(){
return $this->hasMany('App\RegistrationType', 'conference_id');
}
}
Category Model:
class Category extends Model
{
public function conferences(){
return $this->belongsToMany('App\Conference');
}
}
RegistrationType Model:
class RegistrationType extends Model
{
public function conference(){
return $this->belongsTo('App\Conference');
}
}
User Model:
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password'
];
protected $hidden = [
'password', 'remember_token',
];
public function conferences(){
return $this->hasMany('App\Conference');
}
}
For the Category you want a Many-to-Many relation. Because a Conference can have many Categories and a Category can have many Conferences. You have already set this up correctly in your Models.
After you have created your Conference you can add the categories to the Conference, this can be done using the Attach method(Assuming that the given ids do exist, if not you'll have to check that first):
$conference->categories()->attach($request->conference_categories);
For more information about Attach: Laravel Eloquent
For the RegistrationType, I assume from your Models that a Conference can have multiple RegistrationTypes and a RegistrationType can only have 1 Conference.
You will have to add the following $fillables to your RegistrationType Model:
protected $fillable = ['name', 'description', 'capacity', 'conference_id'];
After you have created your Conference you can create the RegistrationType:
RegistrationType::create([
'conference_id' => $conference->id,
'name' => $request->conference_registration_type_name,
'description' => $request->confenrece_registration_type_description,
'capacity' => $request->conference_registration_type_capacity
]);
End result:
$conference = Conference::create([
'name' => $request->conference_name,
'description' => $request->conference_description,
'startDate' => $request->conference_startDate,
'conference_creator_id' => Auth::user()->id,
'conference_creator_name' => $request->conference_creator_name,
'conference_creator_email' => $request->conference_creator_email,
'conference_creator_description' => $request->conference_creator_description
]);
$conference->categories()->attach($request->conference_categories);
RegistrationType::create([
'conference_id' => $conference->id,
'name' => $request->conference_registration_type_name,
'description' => $request->confenrece_registration_type_description,
'capacity' => $request->conference_registration_type_capacity
]);

Saving HasMany Associations Data in CakePHP 3.x

I am having two tables. My primary table is Students. And my secondary table is Exams. I am trying to save both the tables using hasMany and belongsToMany Association. But It is saving data in Student table only, not in Exams. Can any one help me to resolve this problem.
Students Model :
class StudentsTable extends Table {
public function initialize(array $config) {
$this->addBehavior('Timestamp');
parent::initialize($config);
$this->table('students');
$this->primaryKey(['id']);
$this->hasMany('Exams', [
'className' => 'Exams',
'foreignKey' => 'student_id',
'dependent'=>'true',
'cascadeCallbacks'=>'true']);
}
}
Exams Model :
class ExamsTable extends Table {
public function initialize(array $config) {
parent::initialize($config);
$this->table('exams');
$this->primaryKey(['id']);
$this->belongsToMany('Students',[
'className'=>'Students',
'foreignKey' => 'subject_id',
'dependent'=>'true',
'cascadeCallbacks'=>'true']);
}
}
My school.ctp :
echo $this->Form->create();
echo $this->Form->input('name');
echo $this->Form->input('exams.subject', array(
'required'=>false,
'multiple' => 'checkbox',
'options' => array(
0 => 'Tamil',
1 => 'English',
2 => 'Maths')));
echo $this->Form->button(__('Save'));
echo $this->Form->end();
In my controller:
public function school() {
$this->loadModel('Students');
$this->loadModel('Exams');
$student = $this->Students->newEntity();
if ($this->request->is('post')) {
$this->request->data['exams']['subject'] =
implode(',',$this->request->data['exams']['subject']);
$student = $this->Students->patchEntity(
$student, $this->request->data, ['associated' => ['Exams']]
);
if ($this->Students->save($student)) {
$this->Flash->success(__('The user has been saved.'));
} else {
$this->Flash->error(__('Unable to add the user.'));
}
}
}
Patching BelongsToMany Associations
You need to make sure you are able to set exams. Set accessibleFields to allow you to patch associated data
$student = $this->Students->patchEntity(
$student, $this->request->data, [
'associated' => ['Exams'],
'accessibleFields' => ['exams' => true]
]
);
You can also do this with the $_accessible property in the entity.
I've never done hasMany to belongsToMany because i don't think it works that way (I mean no harm in my words.) But I'll try to explain. Your relationships should be both belongsToMany because exams will have many students and students will have many exams. So basically they're the same either way. What you need is another table to connect them which will be called students_exams or exams_students (i think its exams_students because E comes before S) because in cake if you name everything properly most of it happens automatically.
Assuming you know how patchEntity works, creating your $this->request->data properly will patch it automatically and save it in the correct table when you save it. If you have any more questions feel free to ask more. :)

CakePHP 3: hasOne association not getting saved / created

Cake PHP Version: 3.1.5
I have a problem with saving a hasOne association, which works fine on one table but not with a second.
Ticketsand Cashdrafts are related to Cashpositions in a belongsTo relations. Cashpositions holds two FK for their id. So when a new cashposition is auto-created it holds either a ticket_id or a cashdraft_id. The second FK will be null.
The thing is, that the Tickets-Cashpositions saving is working fine, so everytime a ticket is created a related cashposition is created. But it is not working with Cashdrafts-Cashpositions. I don't understand why, because the setup and relations are exactly the same.
Here is the setup:
class CashpositionsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Tickets', [
'foreignKey' => 'ticket_id'
]);
$this->belongsTo('Cashdrafts', [
'foreignKey' => 'cashdraft_id'
]);
}
}
class TicketsTable extends Table
{
public function initialize(array $config)
{
$this->hasOne('Cashpositions', [
'foreignKey' => 'ticket_id'
]);
}
}
class CashdraftsTable extends Table
{
public function initialize(array $config)
{
$this->hasOne('Cashpositions', [
'foreignKey' => 'cashdraft_id'
]);
}
}
And then in the controllers add() functions:
class TicketsController extends AppController
{
public function add($memberId = null)
{
$ticket = $this->Tickets->newEntity();
if ($this->request->is('post')) {
$ticket = $this->Tickets->patchEntity($ticket, $this->request->data, [
// working fine: creates new cashposition for this ticket
'associated' => ['Cashpositions']
]);
if ($this->Tickets->save($ticket)) {
$this->Flash->success(__('ticket saved'));
return $this->redirect(['action' => 'view', $ticket->$id]);
} else {
$this->Flash->error(__('ticket could not be saved'));
}
}
class CashdraftsController extends AppController
{
public function add()
{
$cashdraft = $this->Cashdrafts->newEntity();
if ($this->request->is('post')) {
$cashdraft = $this->Cashdrafts->patchEntity($cashdraft, $this->request->data,[
// fail: no associated record created
'associated' => ['Cashpositions']
]);
if ($this->Cashdrafts->save($cashdraft)) {
$this->Flash->success(__('cashdraft saved.'));
return $this->redirect(['action' => 'view', $cashdraft->id]);
} else {
$this->Flash->error(__('cashdraft could not be saved'));
}
}
}
I debugged the $ticket and $cashdraft. But I cannot say I understand the output because:
The array for the ticket will show every related data but no cashposition, although a new record for it was created successfully...
And the array for the new cashdraft where the related cashposition is NOT created will look like this and say "null" for it:
object(App\Model\Entity\Cashdraft) {
'id' => (int) 10,
'amount' => (float) -7,
'created' => object(Cake\I18n\Time) {
'time' => '2015-12-13T20:03:54+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2015-12-13T20:03:54+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'cashposition' => null, // this part not even showing up for a "ticket" in the debug
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Cashdrafts'
}
In the SQL in DebugKit I can see that for the ticket an INSERT into the related cashpositions table is done. But for cashdrafts there is no INSERT done into the related table. So obviously Cake does not even try to create the associated record.
I'm really out of ideas now! In the database itself both FKs are set up exactly the same, names are correct etc.
Does anybody have an idea what the problem could be or where I could further search for the cause of the second association not working? Thanks!
Ok, so after searching for a million hours I finally realized, that the problem was not with the Model or Controller like I thought. It was (just) the view and the request data not being complete.
Somehow I thought Cake would magically add the entity for the association if non exists even if there is no input for it ;)
In the tickets table for which the saving worked I had an empty input field for a column in Cashpositions that does not even exist anymore and I just hadn't deleted it yet, but it did the trick (don't ask me why).
To fix it now I just put in a hidden input field for the association cashposition.ticket_id and cashposition.cashdraft_id in the add.ctp view for both tables that stays empty. Now the request data contains the array for the association and auto creates a new cashposition with the matching FK every time a new ticket or cashdraft is added.
<!-- Cashdrafts/add.ctp -->
<?php echo $this->Form->input(
'cashposition.cashdraft_id', [
'label' => false,
'type' => 'hidden'
]) ?>
Since I'm just a beginner with this I don't know if this is the best way to go, but it works (finally...)

Categories