Saving associated models in Cakephp 3 - cakephp

I have a form that collects data about an Article, and I want to save that data, as well as for a model called Abstract, where an Article hasMany Abstracts. My models look like this:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AbstractsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Articles');
}
public function validationDefault(Validator $validator)
{
$validator
->notEmpty('body');
return $validator;
}
}
And
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
$this->hasMany('Abstracts');
}
public function validationDefault(Validator $validator)
{
$validator ->notEmpty('category')
return $validator;
}
}
My input form has a field named 'abstracts.body', and in my ArticlesController I have this function:
public function add()
{
$data = $this->request->data;
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
if ($this->request->is('post')) {
$article->user_id = $this->Auth->user('id');
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
$article = $this->Articles->patchEntity($article, $data, [
'associated' => ['Abstracts']
]);
if ($this->Articles->save($article, [ 'validate' => false,
'associated' => ['Abstracts']
]) )
{
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
}
My Abstracts table is pretty straightforward:
CREATE TABLE 'abstracts' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'article_id' INTEGER , 'user_id' INTEGER , 'body' TEXT, 'approved' BOOLEAN )
From debugging I can see that I have the correct 'abstracts' array within my $data (in add()), but it doesn't appear to ever try to save it to the database. Can someone please point out my error? Thanks!

Got it.
I started going wrong here:
My input form has a field named 'abstracts.body'
Because it's a hasMany relationship, I need to have that input be 'abstracts.0.body'
Then the rest of LeWestopher's answer will work-- adding an index to the fields I want to fill in from the Controller, so $data[abstracts][0]['user_id'] => ... and so on. Thanks!

You're post processing your $data['abstracts'] array incorrectly resulting in the association not saving. $data['abstracts'] is expected to be an array of Abstracts. Your issue lies here:
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
You should be able to fix this pretty easily by changing this to:
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
This should correctly iterate over your array of abstracts, set the user_id and approved keys appropriately and then it should save correctly.
CakePHP 3.x Documentation on Saving Associations
EDIT: Very interesting issue indeed. Try it without using patchEntity, and use newEntity by itself instead:
public function add()
{
if ($this->request->is('post')) {
$data = $this->request->data;
// Post process abstracts objects
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
// Build newEntity
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
// Save our entity with associations
if ($this->Articles->save($article, [
'validate' => false,
'associated' => ['Abstracts']
])) {
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
// On save fail
$this->Flash->error(__('Unable to add your article.'));
$this->set('article', $article);
}
}
EDIT 2: Your issue looks like it's definitely in your form helper. Your current form helper input creates an $data array that looks like this:
$data = [
'abstracts' => [
'body' => 'example text'
],
'category' => 'Science'
];
Which SHOULD look like:
$data = [
'abstracts' => [
['body' => 'example text'],
['body' => 'Im your second abstract'],
['body' => 'Abstract three!']
],
'category' => 'Science'
];
The issue lies in:
abstracts.body
Which should read as (in array dot notation):
// abstracts.0.body
echo $this->Form->input('abstracts.0.body', [
'label' => 'summary of article',
'maxlength' =>'440',
'rows' => '7'
]);
I believe that should be the last issue you run into.

Related

Can't save data to database from input form - Laravel 8

it's me again. I have a problem saving data to the database from the input form that I made.
Here is my web route:
Route::get('/admin/data-karyawan', [BasicKaryawanController::class, 'data_karyawan']);
Route::get('/admin/create', [BasicKaryawanController::class, 'index']);
Route::post('/admin/create', [BasicKaryawanController::class, 'simpan']);
Here is my controller:
class BasicKaryawanController extends Controller
{
public function data_karyawan()
{
return view('data-karyawan', [
"title" => "Data Karyawan",
"karyawan" => Karyawan::with(['role', 'user'])->search(request(['search']))->paginate(10)
]);
}
public function index()
{
return view('create', [
"title" => "Tambah Karyawan"
]);
}
public function simpan(Request $request)
{
$validatedData = $request->validate([
'nik' => 'required|max:7',
'nama' => 'required|max:255',
'jk' => 'required|max:1',
'tempat_lahir' => 'required|max:255',
'tanggal_lahir' => 'required|max:255',
'alamat' => 'required|max:255',
'agama' => 'required',
'jabatan' => 'required|max:255',
'id_jabatan' => 'required|max:1',
'divisi' => 'required',
'email' => 'required|email:dns|unique:karyawans',
'password' => 'required|min:5|max:255'
]);
$validatedData['password'] = bcrypt($validatedData['password']);
Karyawan::create($validatedData);
return view('data-karyawan', [
"title" => "Data Karyawan",
"karyawan" => Karyawan::with(['role', 'user'])->search(request(['search']))->paginate(10)
]);
}
and this is my form blade view:
https://codeshare.io/3AzKD1
The code is running well but the form is not saving the data I input from the form to the database. Did I miss something?
Thank you.
This is solved.
I need to fill all the field on the table so it can store to the database.
Yes right, if u r using eloquent create method which accepts an array of attributes, creates a model, and inserts it into the database. The difference between save and create is that save accepts a full Eloquent model instance while create accepts a plain PHP array.
U can take the reference from here

CakePHP 3 how to save related association 1:N in the right way using REST?

I would like to ask how to save related data using REST in CakePHP3?
Lest assume that I have following association Command can have many related tags (see EER diagram below please).
JSON request structure is the following:
{
"command": "whoami",
"description": "SOMETHING",
"public": false,
"tags": [
{"name":"TEST1"},
{"name":"TEST2"}
]
}
I tried to implement saving in associations using
https://book.cakephp.org/3.0/en/orm/saving-data.html#saving-with-associations
But without the luck.
So I did it in the following way:
public function add()
{
$this->request->allowMethod(['post']);
$data = $this->request->getData();
$user = $this->CommonHelper->getUserByToken($this->token);
$command = $this->Commands->newEntity($data);
$tags = $data['tags'];
$command->user = $user;
$command = $this->Commands->patchEntity($command, $data);
if ($this->Commands->save($command)) {
if($tags != null)
$this->addTags($tags, $command);//TODO: DO IT IN THE RIGHT WAY WITH MARSHALLING
$this->apiResponse['data'] = $command;
$this->httpStatusCode = 200;
} else {
$this->httpStatusCode = 400;
}
}
private function addTags($tags, $command)
{
foreach ($tags as $value) {
$tag = $this->Tags->newEntity($value);
$tag->commands_id = $command->id;
$this->Tags->save($tag);
}
}
But this is an only ad-hoc solution, I would like to do it in the right way with use possibilities of CakePHP ORM architecture.
Tags table definition is following:
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('tags');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->belongsTo('Commands', [
'foreignKey' => 'commands_id',
'joinType' => 'INNER'
]);
}
Thanks for any advice.
You can save associated data in this way.
Your data array should be like in this format.
$data = array(
"command"=> "whoami",
"description"=> "SOMETHING",
"public"> false,
"tags": [
array("name" => "TEST1"),
array("name"=> "TEST2")
]
)
$patchedEntity= $this->Commands->patchEntity($saveData,$data,['associated' => ['Tags']]);
$result = $this->Commands->save($patchedEntity);
Define the relations in Commands table should be like this.
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('commands');
$this->hasMany('Tags', [
'foreignKey' => 'command_id'
]);
}
Relation in Tags table should be like this.
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('tags');
$this->belongsTo('Commands', [
'foreignKey' => 'command_id'
]);
}
It was given by bad naming conventions in the model. Using singular in foreign key helped me solved the problem.

How can I create a custom list find methd in cakephp-3?

In my code I am reusing a list-find operation with custom keyField and valueField like this:
->find('list', [
'keyField' => 'key',
'valueField' => 'value'
])
I would like to define a custom list-find method that sets the keyField and valueField, so I don't have to repeat myself. Also it should be chainable with additional custom-find methods.
Edit: Simple and clear solution by calling findList inside the custom finder:
public function findCustomList(Query $query, array $options)
{
return $this->findList($query, [
'keyField' => 'key',
'valueField' => 'value',
]);
}
First Answer:
It is possible to use the formatResults-method of the query-Object to register a formatter that will format the results into the desired list:
public function findCustomList(Query $query, array $options)
{
$query->formatResults(function ($results) {
/** #var \Cake\Collection\CollectionInterface $results */
$formatedResults = [];
foreach ($results as $key => $result) {
$formatedResults[$result->key] = $result->value;
}
return new Collection($formatedResults);
});
return $query;
}

CakePHP 3 : Unknown method

I am creating a function in model to find all related services.
function in ServiceCategory.php
class ServiceCategory extends Entity
{
public function relatedServices($id)
{
return $this->find('all', [
'conditions' => [
'where' => [
'id !=' => $id
],
'limit' => 5
]
]);
}
}
And calling in ServiceCategoriesController.php
public function view($id = null)
{
$serviceCategory = $this->ServiceCategories->get($id, [
'contain' => ['Services']
]);
$relatedServices = $this->ServiceCategories->relatedServices($id);
$this->set('serviceCategory', $serviceCategory);
$this->set('relatedServices', $relatedServices);
$this->set('_serialize', ['serviceCategory']);
}
But it gives Unknown method 'relatedServices'
Is there any thing wrong I am doing ?
The code is in the wrong class
In the question:
class ServiceCategory extends Entity
This is an entity class
$relatedServices = $this->ServiceCategories->relatedServices($id);
This is making a call on a table object, table objects and entities do not inherit from each other, the method is unavailable to the table class.
Move the code to the table class
The direct solution is to move the code to the table class:
// src/Model/Table/ServiceCategoriesTable.php
namespace App\Model\Table;
class ServiceCategoriesTable extends Table
{
public function relatedServices($id)
{
return $this->find('all', [
'conditions' => [
'where' => [
'id !=' => $id
],
'limit' => 5
]
]);
}
Though the arguably correct/better way to do that is to implement a finder:
// src/Model/Table/ServiceCategoriesTable.php
namespace App\Model\Table;
use Cake\ORM\Query;
use \InvalidArgumentException;
class ServiceCategoriesTable extends Table
{
public function findRelatedServices(Query $query, array $options)
{
if (!isset($options['id'])) {
$message = sprintf('No id in options: %s', json_encode($options));
throw new InvalidArgumentException($message);
}
$query->where(['id !=' => $options['id']);
return $query;
}
Which would be called in exactly the same way as other find calls:
$relatedServices = $this->ServiceCategories->find(
'relatedServices',
['id' => $id]
);

Cakephp 3 loading HasOne inside HasMany association

I am using Cakephp 3.2.10
I am able to load associated data with hasOne association and hasMany association. However, I have one hasOne inside the table associated with hasMany, and that gives error.
I have following tables
companies
company_revenues
currencies
states
countries.
CompaniesTable class has hasOne associations with states,countries and it works. CompaniesTable has hasMany association with CompanyRevenuesTable, and it works. CompanyRevenues table has a hasOne association with currencies, this gives error.
My relevant Code :
CompaniesTable.php
<?php
namespace Admin\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
class CompaniesTable extends Table
{
public function initialize(array $config)
{
$this->primaryKey('company_id');
$this->addAssociations(
[
'hasOne' =>
[
'Countries' =>
[ 'className' => 'Countries','foreignKey' => 'id','bindingKey' => 'company_country_id' ]
,'States' =>
[ 'className' => 'States','foreignKey' => 'id','bindingKey' => 'company_state_id' ]
,'Sectors' =>
[ 'className' => 'Sectors','foreignKey' => 'id','bindingKey' => 'company_sector_id' ]
]
,'hasMany' =>
[
'CompanyRevenues' =>
[ 'className' => 'CompanyRevenues','foreignKey' => 'revenue_company_id','bindingKey' => 'company_id' ]
]
]);
}
public function buildRules(RulesChecker $rules)
{
$rules->add( $rules->isUnique(['company_name']) );
return $rules;
}
public function compsTotalCount( Query $query )
{
$result = $query->select(['companiesCount' => $query->func()->count('*')])->first();
return $result;
}
public function findPopular( Query $query )
{
$result = $query->where(['times_viewed >' => 10]);
// debug($result);
return $result;
}
}
?>
CompanyRevenuesTable.php
<?php
namespace Admin\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
class CompanyRevenuesTable extends Table
{
public function initialize(array $config)
{
$this->table('company_revenues');
$this->addAssociations(
[
'hasOne' =>
[
'Currencies' =>
[ 'className' => 'Currencies','foreignKey' => 'id','bindingKey' => 'revenue_currency_id' ]
]
]);
}
}
?>
My CompaniesController.php
profile action
public function profile( $id = null )
{
$company = $this->Companies->find()
->where(['company_id' => $id])
->contain(['CompanyRevenues'])->first();
if( ! $this->request->is('ajax') )
{
$companyRevenuesTable = TableRegistry::get('CompanyRevenues');
$companyRevenues = $companyRevenuesTable->find()
->where(['revenue_company_id' => $company->company_id])
->contain(['Currencies']);
debug($companyRevenues);
}
if( $this->request->is('ajax') )
{
$company = $this->Companies->patchEntity($company, $this->request->data);
$company->company_last_updated = date("Y-m-d H:i:s");
$ajaxRespArr = array();
if( $this->Companies->save($company) )
{
$ajaxRespArr["result"] = 1;
}
else
{
$ajaxRespArr["result"] = 0;
}
$this->set( 'ajaxRespArr',$ajaxRespArr );
$this->set('_serialize', ['ajaxRespArr']);
}
else
{
$this->set('company', $company);
}
}
The debug on $$companyRevenues
gives error
\plugins\Admin\src\Controller\CompaniesController.php (line 92)
object(Cake\ORM\Query) {
(unable to export object: CompanyRevenues is not associated with Currencies)
}
I think the error is because of _ in my table company_revenues.
Can any one guide me please ?
Actually, in the profiles action, i do not want to load company revenues separately, as they come along with company.
I tried the following originally :
$company = $this->Companies->find()
->where(['company_id' => $id])
->contain(
[
'CompanyRevenues' => ["Currencies"]
])->first();
But that give error :
CompanyRevenues is not associated with Currencies InvalidArgumentException
Could this be caused by using Auto-Tables?
Some of the Table objects in your application were created by instantiating "Cake\ORM\Table" instead of any other specific subclass.
This could be the cause for this exception. Auto-Tables are created for you under the following circumstances:
The class for the specified table does not exist.
The Table was created with a typo: TableRegistry::get('Atricles');
The class file has a typo in the name or incorrect namespace: class Atricles extends Table
The file containing the class has a typo or incorrect casing: Atricles.php
The Table was used using associations but the association has a typo: $this->belongsTo('Atricles')
The table class resides in a Plugin but no plugin notation was used in the association definition.
Please try correcting the issue for the following table aliases:
CompanyRevenues
I would suggest adding the reverse relationships as well and see what happens.
By reverse relationships I mean belongsTo in your currencies and companyRevenues classes.
Currencies belongTo companyRevenues and
CompanyRevenues belongTo company class.
See what your debugging provides.

Resources