Cakephp: exposed virtual field are not included in query - mysql

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.

Related

loop logic for storing multi record in laravel?

public function store(Request $request)
{
$productId = $request->id;
$request->validate([
'name' => 'required',
'quantity' => 'required|integer|gt:0',
'fmcode',
'fmcodes',
'area',
'naoffm','glno'
]);
for ($i = 0; $i<=$request->GLNO; $i++){
$product = Product::updateOrCreate(
[
'id' => $productId
],
[
'id'=>$productId,
'name' => $request->name,
'quantity' => $request->quantity,
'fmcode' => $request->FMCODE,
'fmcodes' => $request->FMCODES,
'area' => $request->AREA,
'naoffm' => $request->NAOFFM,
'id'=>$request->GLNO[$i],
'glno'=>$request->GLNO[$i],
]
);
}
return Response()->json($product);
}
selected dropdown of glno values have to store multiple records with glno dropdownlist
and required loop logic to store request data so required solutions for this
You can do multiple insertions using Query Builder. Sample;
DB:table('table_name')->insert(
[
'id' => 1,
'name' => 'mkeremcansev'
],
[
'id' => 2,
'name' => 'John Doe'
]
);

About CakePHP 3 BelongsToMany issue

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'));
}

CakePHP3: how to pass parameter to join()?

I have a DefaultPermissions table, so with a default permissions set, and I want to compare this set with a permissions set affected to a specific site to see if additionnal default permissions exist.
So I wrote the following query:
$defaultPermissions = $this->DefaultPermissions
->find()
->select([
'user_id' => $selUser->id,
'role',
'sequence',
'product',
'module',
'item',
'value',
'visible',
'editable'
])
->join([
'p' => [
'table' => 'permissions',
'type' => 'LEFT',
'conditions' => [
'p.product = DefaultPermissions.product',
'p.module = DefaultPermissions.module',
'p.item = DefaultPermissions.item',
'p.site_id' => $selSite->id // My problem is here!
]
]
],
[
'p.product' => 'varchar',
'p.module' => 'varchar',
'p.item' => 'varchar',
'p.site_id' => 'integer'
])
->where([
'DefaultPermissions.role' => $selUser->role,
'p.item IS' => NULL, // item doit toujours être présent donc si null, la ligne est absente.
'OR' => [['DefaultPermissions.product' => $agplan->product->code], ['DefaultPermissions.product' => '']]
])
->all();
But despite all defaultPermissions can be found for siteselSite->id, the query gives the complete list existing in DefaultPermissions table.
If I remove the line 'p.site_id' => $selSite->id the list is empty as expected so I conclude that the passed parameter is not well interpreted.
How to do it?
There was just a syntax error in the join:
->join([
'p' => [
'table' => 'permissions',
'type' => 'LEFT',
'conditions' => [
'p.product = DefaultPermissions.product',
'p.module = DefaultPermissions.module',
'p.item = DefaultPermissions.item',
'p.site_id = ' => $selSite->id // '=' was missing
]
]
],
....

Yii2: Kartik's DetailView and Select2 loading id's not text

So I'm loading Kartik's Select2 widget in Kartik's DetailView using multiple input:
[
'attribute' => 'characters',
'format' => 'raw',
'type' => DetailView::INPUT_SELECT2,
'value' => function($form, $widget) {
<my_stuff_in_VIEW_mode>
},
'widgetOptions' => [
'options' => ['multiple' => true, 'placeholder' => Yii::t('app', 'Select...')],
'pluginOptions' => [
'allowClear' => true,
'minimumInputLength' => 3,
'maximumInputLength' => 20,
'language' => Yii::$app->language,
'ajax' => [
'url' => \yii\helpers\Url::to(['myAjaxServer']),
'dataType' => 'json',
'delay' => 250,
'data' => new JsExpression('function(params) { return {q:params.term, p:params.page}; }'),
'cache' => true,
],
'escapeMarkup' => new JsExpression("function (markup) { return markup; }"),
'templateResult' => new JsExpression("function (characters) { return '<b>' + characters.text + '</b>'; }"),
'templateSelection' => new JsExpression("function (characters) { return characters.text; }"),
],
],
],
When it comes to Kartik's DetailView, VIEW mode correctly shows my list of characters given by value, but when entering EDIT mode I got this:
which obviously is not what I expected: I want my characters' names rather than their IDs. However if I select new characters from this very same input dropdown, new tags are added which properly show the new names (thus this issue happens when updating data). After I confirm the new data and then try to update, new characters are shown with their IDs again.
JSON data is as follows:
{"results":[{"id":"8","text":"Character8"},{"id":"5","text":"Character5"}],"pagination":{"more":true}}
Any ideas?
UPDATED WITH SOLUTION BY KARTIK BELOW:
Add initValueText as an array of desired attribute values:
'initValueText' => ArrayHelper::getColumn($model->characters, 'name'),
You need to set Select2::initValueText when using Select2 with ajax to show the displayed name instead of id. For multiple select this needs to be setup as an array.

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,
];