Relation between belongsTo and hasMany in cakephp 3.4.9 - cakephp-3.0

I need to save user and invoice data at the same time. When I am submitting the form, the user data is saving, but the invoice table isn't. When I print the request data, I get the following:
object(Cake\ORM\Entity) {
'u_firstname' => 'John',
'u_lastname' => 'den',
'u_email' => 'john#man.com',
'u_phone' => '123',
'password' => '123',
'membership_id' => (int) 1,
'Invoices' => [
'inv_membership_name' => 'basic1',
'inv_membership_cost' => '55',
'inv_purchase_date' => '2017-07-11'
],
'id' => (int) 19,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Users'
}
I am not able to save the Invoice part. I define InvoicesTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('invoices');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
}
and UsersTable like this:
$this->hasMany('Invoices', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
I use the following code to save data:
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->data(), [
'associated' => [
'Invoices'
]
]);
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
}else{
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
}
Please suggest what I'm missing. Or how I should debug.

When you print request data invoices array should be something like this:
object(Cake\ORM\Entity) {
'invoices' => [
[0] => Array
(
['inv_membership_name'] => 'basic1',
['inv_membership_cost'] => '55',
['inv_purchase_date'] => '2017-07-11'
),
[1] => array('...') // if you have multiple records
],
For that change input name to something like this in your form:
invoices[]['inv_membership_name']
invoices[]['inv_membership_cost']
invoices[]['inv_purchase_date']
See here Saving HasMany Associations in CakePhp3.

You are close to the solution but you have the naming conventions wrong in the form. The correct name would be invoices.0.inv_membership_name
invoices.1.inv_membership_name etc. I included the creating inputs for associated data link from the cookbook.
echo $this->Form->control('invoices.0.inv_membership_name');
https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-inputs-for-associated-data

Related

Pretty URL in Yii2 LinkPager with custom params

I need LinkPager to create page URL like /site/page/1/job/2/order-price/3/order-exp/4/.
The route works fine with the URL manager, but LinkPager ignores this route and creates URL with ?foo=bar parameters.
$rules = [
'site/page/<page:\d+>/job/<job_id:\d+>/order-price/<min_price:\d+>/order-exp/<experience:\d+>/' => 'site/index'
];
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'suffix' => '/',
'rules' => $rules,
],
]
$pagination = [
'pageSize' => 1,
'forcePageParam' => true,
'pageSizeParam' => false,
'params' => [
'page' => $this->page,
'job' => $this->job_id,
'order-price' => $this->min_price,
'order-exp' => $this->experience
],
];
You're using incorrect parameters names. If your pattern is site/page/<page:\d+>/job/<job_id:\d+>/order-price/<min_price:\d+>/order-exp/<experience:\d+>/ then parameter names are:
page,
job_id,
min_price,
experience.
You should either change your rule to:
site/page/<page:\d+>/job/<job:\d+>/order-price/<order-price:\d+>/order-exp/<order-exp:\d+>/
Or adjust param names in pagination config:
[
'pageSize' => 1,
'forcePageParam' => true,
'pageSizeParam' => false,
'params' => [
'page' => $this->page,
'job_id' => $this->job_id,
'min_price' => $this->min_price,
'experience' => $this->experience,
],
];

Multiple References to Associated Table in Cakephp

After associating two tables (many weather radar frames which belong to weather radar sites) with belongsTo and hasMany, I was hoping to generate JSON with two "contains" on the same table (and different conditions) so I can list only the frames which have specific conditions. What I currently have doesn't work; it only writes the second contain to the JSON.
Radar Site Table:
...
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('radar_site');
$this->setDisplayField('radar_id');
$this->setPrimaryKey('radar_id');
$this->hasMany('FutureFrame', [
'foreignKey' => 'radar_id'
]);
$this->hasMany('RadarFrame', [
'foreignKey' => 'radar_id'
]);
}
...
Radar Frame Table:
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('radar_frame');
$this->setDisplayField('radar_id');
$this->setPrimaryKey('radar_id');
$this->belongsTo('RadarSite', [
'foreignKey' => 'radar_id'
]);
}
Controller:
public function getRadarData()
{
$radarInfo =
$this->RadarSite->find(
'all',
[
'contain' => [
'RadarFrame' =>[
'sort' => ['RadarFrame.frame_time' => 'DESC'],
'conditions' => [
'RadarFrame.frame_time >' => time() - 60*60,
'RadarFrame.file_on_server =' => 1,
'RadarFrame.radar_type =' => 'velocity'
]
],
'RadarFrame' =>[
'sort' => ['RadarFrame.frame_time' => 'DESC'],
'conditions' => [
'RadarFrame.frame_time >' => time() - 60*60,
'RadarFrame.file_on_server =' => 1,
'RadarFrame.radar_type =' => 'reflectivity'
]
]
]
]
);
$this->set('radarInfo', $radarInfo);
}
JSON output:
...,{
"radar_id": "KHGX",
"radar_name": "Houston\/Galveston, TX",
"elevation": 18,
"tower_height": 20,
"max_latitude": 31.6485,
"min_latitude": 27.2476,
"max_longitude": -92.4946,
"min_longitude": -97.6634,
"latitude": 29.4719,
"longitude": -95.0792,
"radar_frame": [
{
"radar_id": "KHGX",
"radar_type": "reflectivity",
"frame_time": 1495473662,
"is_analyzed": 1,
"average_speed": null,
"average_direction": null,
"file_on_server": 1
},
{
"radar_id": "KHGX",
"radar_type": "reflectivity",
"frame_time": 1495473305,
"is_analyzed": 1,
"average_speed": null,
"average_direction": null,
"file_on_server": 1
},
{
"radar_id": "KHGX",
"radar_type": "reflectivity",
"frame_time": 1495473002,
"is_analyzed": 1,
"average_speed": null,
"average_direction": null,
"file_on_server": 1
}
]
},...
What's the right way to do this? Is there an easy way to add an alias to my controller to differentiate between the two contain queries? I am using CakePHP 3.0.
Thank you!!
Since your conditions are statics, you could put them into the relationship:
Radar Site Table:
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('radar_site');
$this->setDisplayField('radar_id');
$this->setPrimaryKey('radar_id');
$this->hasMany('FutureFrame', [
'foreignKey' => 'radar_id'
]);
$this->hasMany('RadarFrameVelocity', [
'className' => 'RadarFrame',
'foreignKey' => 'radar_id',
'conditions' => [
'RadarFrame.frame_time >' => time() - 60*60,
'RadarFrame.file_on_server =' => 1,
'RadarFrame.radar_type =' => 'velocity'
]
]);
$this->hasMany('RadarFrameReflectivity', [
'className' => 'RadarFrame',
'foreignKey' => 'radar_id',
'conditions' => [
'RadarFrame.frame_time >' => time() - 60*60,
'RadarFrame.file_on_server =' => 1,
'RadarFrame.radar_type =' => 'reflectivity'
]
]);
}
Controller:
public function getRadarData()
{
$radarInfo =
$this->RadarSite->find(
'all',
[
'contain' => [
'RadarFrameVelocity' =>[
'sort' => ['RadarFrameVelocity.frame_time' => 'DESC']
],
'RadarFrameReflectivity' => [
'sort' => ['RadarFrameReflectivity.frame_time' => 'DESC']
]
]
]
);
$this->set('radarInfo', $radarInfo);
}
See also Associations - Linking Tables Together

How to use basic authentication for json requests and form authentication for html requests with Cakephp 3?

I need to filter json requests and allow basic authentication for those requests, while allowing only form authentication for html requests. When I filter the requests in my initialize function in AppController.php:
if ($this->request->is('json')) {
$this->loadComponent('Auth', [
'authorize' => ['Controller'],
'authenticate' => [
'Basic' => [
'fields' => ['username' => 'email', 'password' => 'password'],
'contain' => ['Districts']
]
]
]);
} else {
$this->loadComponent('Auth', [
'authorize' => ['Controller'],
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email', 'password' => 'password'],
'contain' => ['Districts']
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login'
]
]);
}
The json request creates and stores a session allowing the user to then access the rest of the site, including html requests because it has an authorized session. I struggled trying to find what was causing this and eventually found that you have to explicitly declare that the storage medium for the Basic Authentication method as 'Memory'. I'll post the correct code in my answer below.
This question is similar to this one for cakephp 2: CakePHP form authentication for normal requests with basic authentication for JSON
You have to explicitly declare that the Basic Authentication uses Memory for the storage medium, or else it will create a session. Here is the correct code:
if ($this->request->is('json')) {
$this->loadComponent('Auth', [
'authorize' => ['Controller'],
'authenticate' => [
'Basic' => [
'fields' => ['username' => 'email', 'password' => 'password'],
'contain' => ['Districts']
]
],
'storage' => 'Memory'
]);
} else {
$this->loadComponent('Auth', [
'authorize' => ['Controller'],
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email', 'password' => 'password'],
'contain' => ['Districts']
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login'
]
]);
}

Is it possible to use both CakePHP default auth with the hybrid Auth?

Right now I am working on project where we need facebook and twitter login.
And I have also configure the login in my project but now its not authenticate with simple username and password.
This is AppController.php
class AppController extends Controller
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'loginRedirect' => [
'controller' => 'Users',
'action' => 'index'
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login'
],
'authenticate' => [
'ADmad/HybridAuth.HybridAuth'
]
]);
}
}
Its working with only facebook and twitter login,and please let me know how to store user in database after successful login in fb or twitter.
Change above function to this.
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form',
'ADmad/HybridAuth.HybridAuth'
],
'loginRedirect' => [
'controller' => 'Users',
'action' => 'index',
'plugin' => false
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login',
'plugin' => false
]
]);
}
The point here is: just add "Form" under "authenticate".
CakePHP can handle many login types simultaneously.
To store your users in the database just follow the plugin's instructions to set up the users and social_profiles tables.
Then simply add your regular user logic (UsersController + login.ctp, etc, etc).
Here's how I load the Auth component to achieve this:
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
],
],
'ADmad/HybridAuth.HybridAuth' => [
'fields' => [
'provider' => 'provider',
'openid_identifier' => 'openid_identifier',
'email' => 'email'
],
'profileModel' => 'ADmad/HybridAuth.SocialProfiles',
'profileModelFkField' => 'user_id',
'hauth_return_to' => null
],
],
]);

CakePHP 3 hasMany will not pass parent ID to associated children

/* ShootsTable.php Meta Table */
public function initialize(array $config)
{
$this->table('shoots');
$this->displayField('title');
$this->primaryKey('id');
$this->hasMany('ShootMeta');
}
/* ShootMetaTable.php Meta Table */
public function initialize(array $config)
{
$this->table('shoot_meta');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Shoots');
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['shoots_id'], 'Shoots'));
return $rules;
}
/* Shoots.php Controller */
public function add()
{
$shoot = $this->Shoots->newEntity(null);
if ($this->request->is('post')) {
$this->Shoots->patchEntity($shoot, $this->request->data,[
'associated' => ['ShootMeta']
]);
$shoot->set('created_by', 1);
debug($shoot);
if ($this->Shoots->save($shoot,['associated' => ['ShootMeta']])) {
$this->Flash->success('The shoot has been saved.');
// return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The shoot could not be saved. Please, try again.');
}
}
$this->set(compact('shoot'));
$this->set('_serialize', ['shoot']);
}
/* Add.ctp Template */
<div class="shoots form large-10 medium-9 columns">
<?= $this->Form->create($shoot); ?>
<fieldset>
<legend><?= __('Add Shoot') ?></legend>
<?php
echo $this->Form->input('title');
echo $this->Form->input('content');
echo $this->Form->input('datetime', ['label' => 'Date/Time Of Shoot']);
echo $this->Form->input('shoot_meta.0.meta_key', ['type' => 'hidden', 'value' => 'photographer_spaces']);
echo $this->Form->input('shoot_meta.0.meta_value',['label' => 'Photographer Spaces', 'type' => 'number']);
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
</div>
/* debug($shoots) output */
object(App\Model\Entity\Shoot) {
'new' => true,
'accessible' => [
'created_by' => true,
'title' => true,
'content' => true,
'datetime' => true,
'shoot_meta' => true
],
'properties' => [
'title' => '123',
'content' => '123',
'datetime' => object(Cake\I18n\Time) {
'time' => '2015-03-19T07:04:00+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'shoot_meta' => [
(int) 0 => object(App\Model\Entity\ShootMetum) {
'new' => true,
'accessible' => [
'shoots_id' => true,
'meta_key' => true,
'meta_value' => true,
'shoot' => true
],
'properties' => [
'meta_key' => 'photographer_spaces',
'meta_value' => '123'
],
'dirty' => [
'meta_key' => true,
'meta_value' => true
],
'original' => [],
'virtual' => [],
'errors' => [
'shoots_id' => [
'_required' => 'This field is required'
]
],
'repository' => 'ShootMeta'
}
],
'created_by' => (int) 1
],
'dirty' => [
'title' => true,
'content' => true,
'datetime' => true,
'shoot_meta' => true,
'created_by' => true
],
'original' => [],
'virtual' => [],
'errors' => [],
'repository' => 'Shoots'
}
As you can see, the field shoots_id is required, which I would have thought would be automatically passed down (although at this point it hasn't executed any MySQL).
I feel I may have gone about this the wrong way but have spent 2 full days trying to get it right. One of those days was me trying to work out why after baking it had named a lot of the references to ShootMeta to ShootMetum, I thought it had actually corrupted it.
One of the biggest issues I have is knowing where to use shoot_meta, ShootMeta, shootmeta, shootmetum, ShootMetum etc. It feels like a bit of a minefield!
/Update
A dump of the save object below. It is clearly assigning it, it just seems to not be executing it in the SQL?
'shoot_meta' => [
(int) 0 => object(App\Model\Entity\ShootMetum) {
'new' => false,
'accessible' => [
'shoots_id' => true,
'meta_key' => true,
'meta_value' => true
],
'properties' => [
'meta_key' => 'photographer_spaces',
'meta_value' => '123',
'shoot_id' => '2',
'id' => '3'
],
'dirty' => [],
'original' => [],
'virtual' => [],
'errors' => [],
'repository' => 'ShootMeta'
},
Found it.
It is referring to shoot_id when i debug the save
'shoot_meta' => [
(int) 0 => object(App\Model\Entity\ShootMetum) {
'new' => false,
'accessible' => [
'shoots_id' => true,
'meta_key' => true,
'meta_value' => true
],
'properties' => [
'meta_key' => 'photographer_spaces',
'meta_value' => '123',
'shoot_id' => '2',
'id' => '3'
],
'dirty' => [],
'original' => [],
'virtual' => [],
'errors' => [],
'repository' => 'ShootMeta'
},
for some reason it was using the singular name for the association. Changed in the Shoots.php model.
From
$this->hasMany('ShootMeta');
To
$this->hasMany('ShootMeta',[
'foreignKey' => 'shoots_id'
]);
Remove the validation rule for shoots_id. Validation is for data that is posted from the form, and in this case the foreignKey cannot be posted from the Form. You already have rules in your buildRules() method for making sure that value is passed before saving, so removing the validation is 100% safe.
i have same problem like this to, for now my solution is sending associated data to other function/methode and save it.
eg
**
public function add() {
$kantor = $this->Kantor->newEntity($this->request->data);
if ($this->request->is('post')) {
$kantor = $this->Kantor->patchEntity($kantor, $this->request->data);
$rgndata = $this->request->data['Telpkantor'];
$this->request->session()->write('rgndata', $rgndata);
if ($this->Kantor->save($kantor)) {
$result = $this->Kantor->save($kantor);
$this->addTelpKantor($rgndata, $result->id);
$this->Flash->success('The kantor has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The kantor could not be saved. Please, try again.');
}
}
$reffKota = $this->Kantor->ReffKota->find('list', ['limit' => 200]);
$statusKantor = $this->Kantor->StatusKantor->find('list', ['limit' => 200]);
$pimpinan = $this->Kantor->Pimpinan->find('list', ['limit' => 200]);
$jenisTelp = $this->Kantor->Telpkantor->Jenistelp->find('list', ['limit' => 200]);
$this->set(compact('kantor', 'reffKota', 'statusKantor', 'pimpinan', 'jenisTelp'));
$this->set('_serialize', ['kantor']);
}
public function addTelpKantor($rgndata = null, $kantor_id=null) {
if (!empty($rgndata[0]['noTelp'])) {
$this->loadModel('Telpkantor');
foreach ($rgndata as $rgndata) {
$rgndata['kantor_id'] =$kantor_id;
$rgndatasave = $this->Telpkantor->newEntity($rgndata);
$this->Telpkantor->save($rgndatasave);
}
}
}
**