Update a HasMany records - cakephp-3.0

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

Related

Laravel 8: Update a record in database using if statement

Below is the function I used to update my record.
public function store_upd_nilai_uji_program(Request $request)
{
$validated = $request->validate([
'nip' => 'required|numeric|digits_between:18,18',
'n1' => 'nullable|numeric',
'n2' => 'nullable|numeric',
'n3' => 'nullable|numeric',
'n4' => 'nullable|numeric',
'n5' => 'nullable|numeric',
'n6' => 'required|numeric',
'tanggal' => 'required',
'waktu' => 'required',
]);
NilaiUjiProgram::where('nim', $request->nim)->update([
'nip' => $request->nip,
'nilai_kemampuan_dasar_program' => floatval($request->n1),
'nilai_kecocokan_algoritma' => floatval($request->n2),
'nilai_penguasaan_program' => floatval($request->n3),
'nilai_penguasaan_ui' => floatval($request->n4),
'nilai_validasi_output' => floatval($request->n5),
'total' => floatval($request->n6),
'tanggal' => $request->tanggal,
'waktu' => $request->waktu,
]);
return redirect('/prodi/daftar_nilai_uji_program')->with('status', 'Nilai berhasil diperbaharui!');
}
Actually, I want to update the attribute 'nilai_kemampuan_dasar_program', 'nilai_kecocokan_algoritma','nilai_penguasaan_program','nilai_penguasaan_ui', and 'nilai_validasi_output' if only the equal value in $request is NOT NULL. If the value is null, I would like to keep my record fill with NULL. To keep in mind that my table doesn't have any primary key. Do you have any idea how to solve my problem?
NilaiUjiProgram::where('nim', $request->nim)->update([
'nip' => $request->nip,
'nilai_kemampuan_dasar_program' => $request->n1 ? floatval($request->n1) : null,
'nilai_kecocokan_algoritma' => $request->n2? floatval($request->n2) :null,
'nilai_penguasaan_program' => $request->n3 ? floatval($request->n3):null,
'nilai_penguasaan_ui' => $request->n4 ? floatval($request->n4) : null,
'nilai_validasi_output' => floatval($request->n5),
'total' => floatval($request->n6),
'tanggal' => $request->tanggal,
'waktu' => $request->waktu,
]);

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.

Cakephp 3.0 join in three table

I am trying to fetch the data from three table in cakephp 3.0
There is three table
1) size_category
2) sizes
3) sizerelations
Below is the file wise code
Model SizeCategoryTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->table('size_category');
$this->displayField('sizeCat_Id');
$this->primaryKey('sizeCat_Id');
// used to associate the table with user table (join)
$this->belongsTo('Users', [
'className' => 'Users',
'foreignKey' => 'CreatedBy',
'propertyName' => 'user'
]);
$this->hasMany(
'Sizerelations', [
'className' => 'Sizerelations',
'foreignKey' => 'Scat_Id',
'propertyName' => 'sizerelations'
]
);
}
Controller SizeCategoryController
public function index($id = null)
{
$customQuery = $this->SizeCategory->find('all', array(
'contain' => array(
'Users',
'Sizerelations' => array(
'Sizes' => array(
'fields' => array('id', 'Size_Code')
)
)
)
));
//debug();die();
$this->set('sizeCategory', $this->paginate($customQuery));
$this->set('_serialize', ['sizeCategory']);
}
I am greeting the error of Sizerelations is not associated with Sizes

How can I access private value from object

I have the following object
[yii\authclient\clients\Twitter_ef71f520786335688cd762d29aaa2ac98b6bd42f_token] => yii\authclient\OAuthToken Object
(
[tokenParamKey] => oauth_token
[tokenSecretParamKey] => oauth_token_secret
[createTimestamp] => 1447473086
[_expireDurationParamKey:yii\authclient\OAuthToken:private] =>
[_params:yii\authclient\OAuthToken:private] => Array
(
[oauth_token] => 48575497-RTIItaMJBC5LrFl0I2TjYw0ihByC0QuRdRxf44tVX
[oauth_token_secret] => IcdvTgwiclWGNhX4TrsDjEkEdNwVF7l1XzXmgpNstGfkV
[user_id] => **********
[screen_name] => ***********
[x_auth_expires] => 0
)
)
and in the above object I want to access the parameter oauth_token and oauth_token_secret which are under _params:yii\authclient\OAuthToken:private
How do I get those values in php?
https://github.com/yiisoft/yii2-authclient/blob/master/OAuthToken.php#L86
yii\authclient\OAuthToken has public method getParams() and getParam($name)

How to connect 3 tables in yii2 and display in Gridview then make sorting work correctly

I have used the gii tool to create crud application. I have 3 tables the tbl_targetcities, lib_cities, and lib_provinces. I was able to connect lib_cities to tbl_targetciteis but not the lib_provinces. And also the sorting of city / Municipality does not work. It seems that it sorts according ti the ID.
tbl_target_cities
lib_cities
lib_provinces
sample View
So far here is my relation in the model.
public function getCityName()
{
return $this->hasOne(LibCities::className(),['city_code'=>'city_code']);
}
in my view file...
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
[
'attribute'=>'city_code',
'value'=>'cityName.city_name'
],
[
'attribute'=>'prov code',
'value'=>'cityName.city_name'
],
'kc_classification',
'cluster',
'grouping',
'priority',
'launch_year',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
How to display the prov_name from lib_provinces???
EDIT to answer user2839376 question in the comment box
IN THE SEARCH MODEL CLASS
$query = TblSpBub::find();
$query->joinWith('brgyCode')->joinWith(['cityCode'])->joinWith(['cityCode.provCode']);
$covered= LibAreas::find()->where(['user_id'=>yii::$app->user->identity->id])->all();
$query->all();
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort'=> ['defaultOrder' => ['id'=>SORT_DESC]],
]);
$dataProvider->sort->attributes['city'] = [
'asc' => ['lib_Cities.city_name' => SORT_ASC],
'desc' => ['lib_Cities.city_name' => SORT_DESC],
];
$dataProvider->sort->attributes['province'] = [
'asc' => ['lib_provinces.prov_name' => SORT_ASC],
'desc' => ['lib_provinces.prov_name' => SORT_DESC],
];
In LibCities model add new relation:
public function getProvince()
{
return $this->hasOne(LibProvince::className(),['prov_code'=>'prov_code']);
}
And change getCityName relation. You should add with() for relation:
public function getCityName()
{
return $this->hasOne(LibCities::className(),['city_code'=>'city_code'])->with(['province']);
}
And in view correct your columnto this:
[
'attribute'=>'prov code',
'value'=>'cityName.province.prov_name'
],
You have to use the function relations() in models.
In tbl_target_cities model:
public function relations()
{
return array(
'city' => array(self::HAS_ONE, 'LibCities', 'city_code'),
);
}
In LibCities model :
public function relations()
{
return array(
'province' => array(self::HAS_ONE, 'LibProvinces', 'prov_code'),
'targets' => array(self::HAS_MANY, 'TargetCity', 'city_code',
);
}
This will allowed you to jump throw the LibCities model,
now you can simply acces to prov name like this :
$model->city->province->prov name;
Note : You need to have the 3 models defined.
EDIT
array(
'name' => 'province name',
'value' => $data->city->province->prov_name;
),
Done it, Heres how.
in addition to the above code (original post)
// in Model I added an additional function
public function getTaskowner()
{
return $this->hasOne(Tasks::className(), ['id' => 'task_id'])
->with(
['location','taskowner']
);
}
and in view i did this
....
'columns' => [
....
[
'class' => 'kartik\grid\DataColumn',
'value'=> 'tasks.location.taskowner.name',
.....
],
.....
and it worked
key points. used an array with the 'with->(..)' to include both then in the view added 'tasks.location.taskowner.name', to join them all