About CakePHP 3 BelongsToMany issue - cakephp-3.0

I have an issue with the following case :
Users Table
id, profile_id, name, created, modified
Profiles Table
id, first_name, last_name, gender
Businesses table
id, name, created, modified
I have a relation table many to many to link profiles to businesses : businesses_profiles
id, business_id, profile_id, created, modified
When i try to create a new business, I would like to link directly logged in user profile id to the business I'm creating.
in my profileTable, I've added in initialize() :
$this->belongsToMany('Businesses', [
'alias' => 'Businesses',
'foreignKey' => 'profile_id',
'targetForeignKey' => 'business_id',
'joinTable' => 'businesses_profiles'
]);
in my businessesTable, I've also put in initialize() method:
$this->belongsToMany('Profiles', [
'alias' => 'Profiles',
'foreignKey' => 'business_id',
'targetForeignKey' => 'profile_id',
'joinTable' => 'businesses_profiles'
]);
In each entities Business & Profile, I put respectively in right context :
protected $_accessible = [
'*' => true,
'id' => false,
'businesses' => true,
'_joinData' => true
];
and :
protected $_accessible = [
'name' => true,
'slug' => true,
'active' => true,
'hash' => true,
'data' => true,
'approved' => true,
'created' => true,
'modified' => true,
'profiles' => true,
'_joinData' => true
];
Nothings work about saving in my businesses_profiles table.
Thanks in advance for your help,
Best,
Laurent.

Thanks a lot for your help. I've found the solution by using the link() method provided by CakePHP.
I'll share my add function here, if it can help others :
public function add()
{
$business = $this->Businesses->newEntity();
if ($this->request->is('post')) {
$business = $this->Businesses->patchEntity($business, $this->request->getData());
$business->set('hash');
$business->set('active', 0);
$business->set('slug');
$profile = TableRegistry::getTableLocator()->get('Profiles')->get($this->_currentUser->profile_id);
if ($this->Businesses->save($business)) {
**$this->Businesses->Profiles->link($business, [$profile]);**
$this->Flash->success(__('The business has been saved.'));
return $this->redirect(['action' => 'index']);
}
else
{
debug($business->getErrors()); // debug
$this->Flash->error(__('The business could not be saved. Please, try again.'));
}
}
$this->set(compact('business'));
}

Related

Cakephp: exposed virtual field are not included in query

I read some other questions but they are very old. I'm using CakePHP 3.7.9.
I read the documentation about virtual fields, here and here.
So far:
Proforma.php
class Proforma extends Entity
{
protected $_accessible = [
'customer_id' => true,
'proforma_number' => true,
'proforma_date' => true,
'payment_date' => true,
'proforma_state_id' => true,
'customer' => true,
'proforma_state' => true,
'item_proformas' => true
];
protected $_virtual = [
'total'
];
protected function _getTotal()
{
$q = TableRegistry::getTableLocator()->get('item_proformas')->find();
$v = $q
->select(['total' => $q->func()->sum('total')])
->where(['proforma_id' => $this->id])
->first()['total'];
if ($v == null) $v = 0;
return $v;
}
}
In views I can easily access to this field:
<td class="text-right"><?= $proforma->item_proformas ? $this->Number->currency($proforma->total) : '' ?></td>
But when I try to make a query in Controller, i.e.:
$query = $this->Proformas->find();
debug($query);
$query->select(['value' => $query->func()->sum('total')]);
The field total is not found. Here the output of the debug:
object(Cake\ORM\Query) {
'(help)' => 'This is a Query object, to get the results execute or iterate it.',
'sql' => 'SELECT Proformas.id AS `Proformas__id`, Proformas.customer_id AS `Proformas__customer_id`, Proformas.proforma_number AS `Proformas__proforma_number`, Proformas.proforma_date AS `Proformas__proforma_date`, Proformas.payment_date AS `Proformas__payment_date`, Proformas.proforma_state_id AS `Proformas__proforma_state_id` FROM proformas Proformas',
'params' => [],
'defaultTypes' => [
'Proformas__id' => 'integer',
'Proformas.id' => 'integer',
'id' => 'integer',
'Proformas__customer_id' => 'integer',
'Proformas.customer_id' => 'integer',
'customer_id' => 'integer',
'Proformas__proforma_number' => 'string',
'Proformas.proforma_number' => 'string',
'proforma_number' => 'string',
'Proformas__proforma_date' => 'date',
'Proformas.proforma_date' => 'date',
'proforma_date' => 'date',
'Proformas__payment_date' => 'date',
'Proformas.payment_date' => 'date',
'payment_date' => 'date',
'Proformas__proforma_state_id' => 'integer',
'Proformas.proforma_state_id' => 'integer',
'proforma_state_id' => 'integer'
],
'decorators' => (int) 0,
'executed' => false,
'hydrate' => true,
'buffered' => true,
'formatters' => (int) 0,
'mapReducers' => (int) 0,
'contain' => [],
'matching' => [],
'extraOptions' => [],
'repository' => object(App\Model\Table\ProformasTable) {
'registryAlias' => 'Proformas',
'table' => 'proformas',
'alias' => 'Proformas',
'entityClass' => 'App\Model\Entity\Proforma',
'associations' => [
(int) 0 => 'customers',
(int) 1 => 'proformastates',
(int) 2 => 'invoices',
(int) 3 => 'itemproformas'
],
'behaviors' => [],
'defaultConnection' => 'default',
'connectionName' => 'default'
}
}
Why even if the virtual field is exposed it's not inserted into the query?
The documentation linked above says:
Do bear in mind that virtual fields cannot be used in finds. If you want them to be part of JSON or array representations of your entities, see Exposing Virtual Fields.
and then:
By default virtual fields are not exported when converting entities to arrays or JSON. In order to expose virtual fields you need to make them visible. When defining your entity class you can provide a list of virtual field that should be exposed
So exposing virtual fields should let me to use them in finds.
Sadly Virtual fields are not calculated at the point the query is run. Here are some alternative approaches you may find helpful.
Create View of the actual table ( handle the sum at the DB level)
Create A beforeFind event in the table model.

Checking existing record based on two fields

I am trying to insert some record by checking if certain conditions are met. What I am planning to do is to restrict the users from voting twice daily. I am using email and date as parameters.
public function store(Request $request)
{
$this->validate($request, [
'flavour' => 'required|string',
'email' => 'required|email',
'lastname'=> 'required|string',
'firstname' => 'required|string'
]);
$vote = new Vote();
//$currentDate = Carbon::now()->toDateString();
$today = Carbon::now()->format('Y-M-D');
$dup = Vote::where(['email' => $request->email, 'created_at' => $today ])->get();
if (!$dup)
{
$vote->firstname = $request->firstname;
$vote->lastname = $request->lastname;
$vote->email = $request->email;
$vote->flavour = $request->flavour;
$vote->voter_ip = $request->ip();
}
else {
return response()->json([
'message' => 'You have voted today, please wait till another day!'
]);
}
if (auth()->user()->votes()->save($vote))
return response()->json([
'success' => true,
'data' => $vote->toArray()
]);
else
return response()->json([
'success' => false,
'message' => 'Vote could not be added'
], 500);
}
The problem I am encountering now is, it doesn't take any record as it keeps displaying the message "You have voted today, please wait till another day", even if I insert a new email and/or a current user with different emails.
First, I had to create a field in my migration called "voted_at" and rearranged the code to check for the existing record based on the voted_at and email of the user
public function store(Request $request)
{
$this->validate($request, [
'flavour' => 'required|string',
'email' => 'required|email',
'lastname'=> 'required|string',
'firstname' => 'required|string'
]);
$today = Carbon::now()->toDateString();
$dup = Vote::where(['email' => $request->email, 'voted_at' => $today ])->exists();
if ($dup){
return response()->json([
'data' => $dup,
'message' => 'You have voted today, please wait till another day!'
]);
}
else {
$vote = new Vote();
$vote->firstname = $request->firstname;
$vote->lastname = $request->lastname;
$vote->email = $request->email;
$vote->flavour = $request->flavour;
$vote->voter_ip = $request->ip();
$vote->voted_at = $today;
if (auth()->user()->votes()->save($vote))
return response()->json([
'success' => true,
'data' => $vote->toArray()
]);
else
return response()->json([
'success' => false,
'message' => 'Vote could not be added'
], 500);
}
}
Thanks

cakephp 3.x cascade delete not working

I have 3 tables names articles,comments,addresses.
articles -> fields(id,title,body)
comments -> fields(id,article_id,comment)
addresses-> fields(id,article_id,address)
and in my articles controller i have kept dependent=>true and also cascadeCallbacks=>true. First i tried with dependent => true,i dint work then added cascade, still it does not work. Below is my code.
$this->hasMany('Comments', [
'className' => 'Comments',
'dependent' => true,
'cascadeCallbacks' => true,
]);
$this->hasOne('Addresses',[
'dependent' => true,
'cascadeCallbacks' => true,
]);
but while deleting articles, associated records are not deleted.
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$article = $this->Articles->get($id);
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The article has been deleted.'));
} else {
$this->Flash->error(__('The article could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
Please tell me what is the mistake i did. or any code need to be added or changed????
Pl help
Try this :
$this->hasMany('Comments', [
'foreignKey' => 'article_id',
'dependent' => true,
'cascadeCallbacks' => true
]);

Update a HasMany records

I am converting a working app from cakephp2 to cakephp3. I'm struggling to get a form that updates hasMany records to work.
The app has the following structure:
MODELS:
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AwardsTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('Levels', ['sort' => 'sort_order']);
}
}
namespace App\Model\Entity;
use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;
class Award extends Entity
{
protected $_accessible = [
'name' => true,
'url' => true,
'organisation' => true,
'detail' => true,
'logo' => true,
'levels' => true,
'Levels' => true
];
}
IN THE FORM:
<?= $this->Form->input("levels.$i.name", 'label'=>false,'type'=>'text','value' => $award->name]);?>
<?= $this->Form->input("levels.$i.id", ['value' => $award->id]); ?>
CONTROLLER
$this->Awards->patchEntity($award, $this->request->data, ['associated' => ['Levels']]);
if ($this->Awards->save($award)) {
$this->Flash->success(__('Your Award has been saved.'));
$this->redirect(['action' => 'index']);
}
This seems inline with what is recommended here: http://book.cakephp.org/3.0/en/views/helpers/form.html#associated-form-inputs
I've tried a few variations with capitalisation & pluralisation. The award data saves correctly but the associated levels data does not save.
What am I missing to get the has_many association data to save?
EDIT: Example Data array submitted:
2016-01-28 23:32:56 Error: Array
(
[id] => 4
[name] => test award
[organisation] => test org
[url] => http://www.example.com
[detail] =>
[levels] => Array
(
[0] => Array
(
[name] => test 1
[id] => 4
)
[11] => Array
(
[name] => test
[id] => 16
)
)
[image] => Array
(
[name] =>
[type] =>
[tmp_name] =>
[error] => 4
[size] => 0
)
)
In LevelEntity verify if 'id' is accessible
protected $_accessible = [
'*' => true,
'id' => true,
];

Two fields in a table related to a field in another table

I have a model/table solicitation with 2 fields (user_id and analyst_id) related to field id in model table user.
In my view, i have a gridview with fields:
[
'attribute' => 'user_id',
'enableSorting' => true,
'value' => function ($model) {
return $model->user->username;
},
]
How do same with analyst_id (i need show the username based another id) ?
UPDATE
Simply create another relation for analyst:
Add in model:
public function getAnalyst()
{
return $this->hasOne(User::className(), ['id' => 'analyst_id']);
}
Displaying in GridView:
[
'attribute' => 'analyst_id',
'enableSorting' => true,
'value' => function ($model) {
return $model->analyst->username;
},
],
Or if the analyst might not exist replace return part:
return $model->analyst ? $model->analyst->username : null;
I think
'value' => function ($model) {
return User::findOne($model->analyst_id)->username;
},
PS Add the correct namespace for User