I am trying something more complicated. I have an Item which stores all general items, I have a Product which is an item and I have a Good which is a product and a item. So I have a form for entering value for the good and it shall save to all model related tables (items, products, goods). The reason for so many tables is because all tables shall have an id which is used later, example: product shall have its id which is used later for selling. Here is the controller:
public function add() {
$this->load();
if ($this->request->is('post')) {
$this->Item->create();
$this->request->data['Item']['code'] = $finalCode;
$this->request->data['Item']['is_deleted'] = false;
$item = $this->Item->save($this->request->data);
if(!empty($item)){
$this->request->data['Product']['item_id'] = $this->Item->id;
$this->request->data['Good']['item_id'] = $this->Item->id;
debug($this->request->data['Product']['item_id']);
debug($item);
$this->Item->Product->save($this->request->data);
$this->request->data['Good']['pid'] = $this->Product->id;
$this->Item->Good->save($this->request->data);
}
if($this->Good->validationErrors || $this->Item->validationErrors || $this->Product->validationErrors){
//ERRORS
}
else{
//FAILS
}
}
}
EDIT: I have changed the controller and now the Item is never saved but Product and Good is saved and they are all mapped well, ids are ok but Item is not even in the db, althrough Product and Good have item_id set to a value which should be next in the db.
class Good extends AppModel {
public $belongsTo = array(
'Item' => array(
'className' => 'Item',
'foreignKey' => 'item_id',
),
'Product' => array(
'className' => 'Product',
'foreignKey' => 'pid',
)
);
}
class Product extends AppModel {
public $hasOne = array(
'Good' => array(
'className' => 'Good',
'foreignKey' => 'pid',
'dependent' => false,
),
);
}
class Item extends AppModel{
public $hasMany = array(
'Good' => array(
'className' => 'Good',
'foreignKey' => 'item_id',
'dependent' => false,
),
'Product' => array(
'className' => 'Product',
'foreignKey' => 'item_id',
'dependent' => false,
),
);
}
Even the debugged $item looks ok but is not saved:
array(
'Item' => array(
'name' => 'Microcontrollers',
'description' => 'Wire Jumpers Female-to-Female 30 cm',
'weight' => '22',
'measurement_unit_id' => '7',
'item_type_id' => '29',
'code' => 'GOD-34',
'is_deleted' => false,
'modified' => '2019-10-22 12:37:53',
'created' => '2019-10-22 12:37:53',
'id' => '120'
),
'Good' => array(
'status' => 'development',
'hts_number' => '8473 30 20',
'tax_group' => '20%',
'eccn' => 'EAR99',
'release_date' => array(
'month' => '10',
'day' => '22',
'year' => '2019',
'hour' => '10',
'min' => '10',
'meridian' => 'am'
),
'is_for_distributors' => '1'
),
'Product' => array(
'project' => 'neqwww'
)
)
I think the problem is with your code at the lines where you are saving the data.
$this->Item->create();
$this->Good->create();
$this->Product->create();
What is $this? If you create a item with "$this" and later try to create a "product", "$this" will not have the item_id.
Try using something like this to save the item created.
$item = $this->Item->create();
Then, with that $item created, you could create a $product with the $item->id
Update:
From cakephp documentation.
// Create: id isn't set or is null
$this->Recipe->create();
$this->Recipe->save($this->request->data);
// Update: id is set to a numerical value
$this->Recipe->id = 2;
$this->Recipe->save($this->request->data);
You must use $this->Item->save($data) to save the information into database.
https://book.cakephp.org/2.0/en/models/saving-your-data.html
Maybe the method create() is a bit unclear. It is used to restart the model state.
So, it would be
$item_saved = $this->Item->save($data['Item']);
$data['Product']['item_id'] = $this->Item->getLastInsertId();
$product_saved = $this->Product->save($data['Product']);
Edit 2:
Maybe it is because you didn't use create() before save the Item. Try this please:
$this->Item->create();
$item_saved = $this->Item->save($data['Item']);
$data['Product']['item_id'] = $this->Item->getLastInsertId();
$product_saved = $this->Product->save($data['Product']);
I am currently working on a web application (written in PHP, based on Yii), which, among other things, lets you connect different price values for products. Each product can have multiple prices, but the system is built in a way that I can easily (and correctly) determine the type of the price field - so while each product can have multiple price fields, it can only have one of each type of price fields.
The part where I stuck is when I have to display the stored value of these fields in a list, and order the list view by them - I can display them correctly and sort them by one column, but as soon as I try to make the list to be sortable by all of the columns (not at the same time, of course), the rows start to display the wrong values.
Here are the relevant pieces of code:
In the model, at relations:
'productPriceConnects' => array(Product::HAS_MANY, 'ProductPriceConnect', 'product_id'),
'price1' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceBeszerzesi.product_price_field_id=:product_price_field_id AND priceBeszerzesi.active = 1',
'params' => array(':product_price_field_id' =>ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 6))->id),
'alias' => 'priceBeszerzesi',
),
'price2' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceEladasi.product_price_field_id=:product_price_field_id AND priceEladasi.active = 1',
'params' => array(':product_price_field_id' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 1))->id),
'alias' => 'priceEladasi'
),
'price3' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceAkcios.product_price_field_id=:product_price_field_id AND priceAkcios.active = 1',
'params' => array(':product_price_field_id' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 5))->id),
'alias' => 'priceAkcios'
),
In the model, at search:
...
$criteria->with('price1', 'price2', 'price3);
...
$criteria->compare('price1.price', $this->beszerzesi, true);
$criteria->compare('price2.price', $this->eladasi, true);
$criteria->compare('price3.price', $this->akcios, true);
...
$sort->attributes = array(
'price1' =>array(
'asc' => 'priceBeszerzesi.price ASC',
'desc' => 'priceBeszerzesi.price DESC',
),
'price2' =>array(
'asc' => 'priceEladasi.price ASC',
'desc' => 'priceEladasi.price DESC',
),
'priceAkcios' =>array(
'asc' => 'price3.price ASC',
'desc' => 'price3.price DESC',
),
'*'
);
...
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'sort' => $sort,
'pagination' => array(
'pageSize' => 10,
),
)
);
The columns' data in gridview:
'eladasi' => array(
'name' => 'price2',
'header' => 'Eladási ár',
'type' => 'raw',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'value' => '!empty($data->price2->price) ? $data->price2->price == "" ? "-" : $data->price2->price : "-"',
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'akcios' => array(
'name' => 'priceAkcios',
'header' => 'Akciós',
'value' => '!empty($data->price3->price) ? $data->price3->price == "" ? "-" : $data->price3->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'beszerzesi' => array(
'name' => 'price1',
'header' => 'Beszerzési ár',
'type' => 'raw',
'value' => '!empty($data->price1->price) ? $data->price1->price == "" ? "-" : $data->price1->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
This code makes it possible to sort the list by all three of the relation-dependent columns, but every column displays the same value - the value of the last relation in the with array. If the last element in the array is price3, then the columns display the value of the price3 relation.
When I remove all relation names from the with array expect one, I can sort the list by that column, but not the others.
My question is this:
Is there any way to
1) surely add any number of relations to a model, connecting to the same db field, but depending on conditions,
2) and display these values WHILE enabling sorting based on them?
Find solution below:
I created these tables at my system and used your relation and gridview code. I made some changes in that code and now in below code searching and sorting is working perfectly.
I defined three variable in model class i.e.
public $beszerzesi;
public $eladasi;
public $akcios;
Then I changed the param's name in relation arraye used with left join. This was the main issue in your code. You used same name for parameters i.e. :product_price_field_id I assigned different name for each parameters. While yii prepare sql query it will replace the parameters which are assigned to query. In your case it was replacing same value for all three parameters.
Also i made some changes in sort and compare attributes passed in CActiveDataProvider. You can find all changes in below model file.
Product.php
<?php
/**
* This is the model class for table "product".
*
* The followings are the available columns in table 'product':
* #property integer $id
* #property string $name
*/
class Product extends CActiveRecord
{
public $beszerzesi;
public $eladasi;
public $akcios;
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'product';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('name', 'required'),
array('name', 'length', 'max' => 100),
// The following rule is used by search().
// #todo Please remove those attributes that should not be searched.
array('id, name, beszerzesi, eladasi,akcios', 'safe', 'on' => 'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'productPriceConnects' => array(Product::HAS_MANY, 'ProductPriceConnect', 'product_id'),
'price1' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceBeszerzesi.product_price_field_id=:product_price_field_id1 AND priceBeszerzesi.active = 1',
'params' => array(':product_price_field_id1' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 6))->id),
'alias' => 'priceBeszerzesi',
),
'price2' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceEladasi.product_price_field_id=:product_price_field_id2 AND priceEladasi.active = 1',
'params' => array(':product_price_field_id2' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 1))->id),
'alias' => 'priceEladasi'
),
'price3' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceAkcios.product_price_field_id=:product_price_field_id3 AND priceAkcios.active = 1',
'params' => array(':product_price_field_id3' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 5))->id),
'alias' => 'priceAkcios'
),
);
}
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'name' => 'Name',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
*
* Typical usecase:
* - Initialize the model fields with values from filter form.
* - Execute this method to get CActiveDataProvider instance which will filter
* models according to data in model fields.
* - Pass data provider to CGridView, CListView or any similar widget.
*
* #return CActiveDataProvider the data provider that can return the models
* based on the search/filter conditions.
*/
public function search()
{
// #todo Please modify the following code to remove attributes that should not be searched.
$criteria = new CDbCriteria;
$criteria->with = array('price1', 'price2', 'price3');
$criteria->compare('id', $this->id);
$criteria->compare('name', $this->name, true);
$criteria->compare('priceBeszerzesi.price', $this->beszerzesi, true);
$criteria->compare('priceEladasi.price', $this->eladasi, true);
$criteria->compare('priceAkcios.price', $this->akcios, true);
// $criteria->attributes = ;
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'sort' => array(
'attributes' => array(
'beszerzesi' => array(
'asc' => 'priceBeszerzesi.price',
'desc' => 'priceBeszerzesi.price DESC',
),
'eladasi' => array(
'asc' => 'priceEladasi.price',
'desc' => 'priceEladasi.price DESC',
),
'akcios' => array(
'asc' => 'priceAkcios.price',
'desc' => 'priceAkcios.price DESC',
),
'*'
)
),
'pagination' => array(
'pageSize' => 10,
),
)
);
}
/**
* Returns the static model of the specified AR class.
* Please note that you should have this exact method in all your CActiveRecord descendants!
* #param string $className active record class name.
* #return Product the static model class
*/
public static function model($className = __CLASS__)
{
return parent::model($className);
}
}
gridview code
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id' => 'product-grid',
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
'id',
'name',
'eladasi' => array(
'name' => 'eladasi',
'header' => 'Eladási ár',
'type' => 'raw',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'value' => '!empty($data->price2->price) ? $data->price2->price == "" ? "-" : $data->price2->price : "-"',
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'akcios' => array(
'name' => 'akcios',
'header' => 'Akciós',
'value' => '!empty($data->price3->price) ? $data->price3->price == "" ? "-" : $data->price3->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'beszerzesi' => array(
'name' => 'beszerzesi',
'header' => 'Beszerzési ár',
'type' => 'raw',
'value' => '!empty($data->price1->price) ? $data->price1->price == "" ? "-" : $data->price1->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
array(
'class' => 'CButtonColumn',
),
),
)); ?>
You can found step by step guide for the searching and sorting on relation data at Searching and sorting by related model in CGridView | Wiki | Yii PHP Framework
I have an app with two associated models: User and Course, which are related by a HABTM association.
There is a registration form where a new user may enter a username and select the courses that they are a part of (from a list of existing courses in the database), only the form only saves the new users - it doesn't save anything to the join table.
The join table (courses_users) has columns course_id and user_id, and the two models look like this:
// User.php
class User extends AppModel {
public $name = 'User';
public $hasAndBelongsToMany = array(
'Courses' => array(
'className' => 'Course',
'joinTable' => 'courses_users',
'foreignKey' => 'user_id',
'associatedForeignKey' => 'course_id'
)
);
}
// Course.php
class Course extends AppModel {
public $name = 'Course';
public $hasAndBelongsToMany = array(
'Users' => array(
'className' => 'User',
'joinTable' => 'courses_users',
'foreignKey' => 'course_id',
'associatedForeignKey' => 'user_id'
)
);
}
In addition, this is the controller action:
// IdentificationController.php
public function register() {
if ($this->request->is('POST')) {
$data = $this->request->data;
$username = $data['User']['username'];
$saved = $this->User->save($data, array('deep' => true));
//debug($data);
if ($saved) {
$this->_set_new_user_session($username);
//$log = $this->User->getDataSource()->getLog(false, false);
//debug($log);
$this->redirect(array('controller' => 'users', 'action' => 'index'));
}
}
// Not redirecting
$courses = $this->Course->find('list', array('Course.name'));
//debug($courses);
$this->set(compact('courses'));
}
And this is the form, sans container divs:
<?php
echo $this->Form->create('User', array(
'inputDefaults' => array(
'label' => false,
'div' => false
),
'url' => '/identification/register'
));
echo $this->Form->input('username', array(
'error' => false,
'autofocus' => true,
'required' => true,
'pattern' => '[a-zA-Z0-9]{3,16}'
));
if ($this->Form->isFieldError('username')) {
echo $this->Form->error('username', null, array('wrap' => 'small', 'class' => 'error'));
}
echo $this->Form->input('Course.Course', array(
'error' => false,
'required' => true
));
if ($this->Form->isFieldError('courses')) {
echo $this->Form->error('course', null, array('wrap' => 'small', 'class' => 'error'));
}
echo $this->Form->button('Register', array(
'div' => false,
'type' => 'submit'
));
echo $this->Form->end();
?>
When I call debug($data), the right data seems to be passed from the form to the controller:
array(
'User' => array(
'username' => 'test63apd'
),
'Course' => array(
'Course' => array(
(int) 0 => '1'
)
)
)
But nothing happens to the join table, and there is no mention of the join table in the DataSource log:
array(
'log' => array(
(int) 0 => array(
'query' => 'BEGIN',
'params' => array(),
'affected' => null,
'numRows' => null,
'took' => null
),
(int) 1 => array(
'query' => 'INSERT INTO `xray2`.`users` (`username`) VALUES ('test06apd')',
'params' => array(),
'affected' => (int) 1,
'numRows' => (int) 1,
'took' => (float) 1
),
(int) 2 => array(
'query' => 'COMMIT',
'params' => array(),
'affected' => (int) 1,
'numRows' => (int) 1,
'took' => (float) 1
)
),
'count' => (int) 3,
'time' => (float) 2
)
Am I missing something really obvious here, or is there some quirk of Cake that I have yet to discover?
You're only telling Cake to save the primary model data. You need to change this line:-
$saved = $this->User->save($data, array('deep' => true));
To:-
$saved = $this->User->saveAssociated($data, array('deep' => true));
saveAssociated() tells Cake to save the current model and its associated data. You also shouldn't need to pass array('deep' => true) as you are only saving data to a directly associated model. So it would be better (and safer) to use:-
$saved = $this->User->saveAssociated($data);
Update
There is an issue with the data being saved as you are not using the alias defined in your association for the data. So when Cake attempts to save the associated data it can't see any. According to your code your User model has and belongs to many Courses (plural) but your save data uses Course (singular). Therefore, your form should be:-
echo $this->Form->input('Courses.Courses', array(
'error' => false,
'required' => true
));
It should be noted that Cake naming conventions use singular forms for model names, so it would be better to use Course in the association rather than Courses. If you change this then your association can be simplified to the following:-
public $hasAndBelongsToMany = array(
'Course'
);
Cake will understand how to handle the join table and foreign keys as they conform to the naming convention. Then you wouldn't need to change your form.
I have User model which contain a function that calculates the average revenue per user. Now i want apply sorting in CGridView on the column which is associated with getAvgRevenue() function. While license is relation in the User model.
Here is my code,
public class User{
$user_id;
$email;
public function getAvgRevenue(){
$count = 0;
$revenue = 0;
foreach ($this->license as $license){
$revenue += $license->price;
$count++;
}
if($count!= 0){
$averageRevenue = $revenue/$count;
return $averageRevenue;
}
else{
return null;
}
}
}
In Controller
$modelUser = new CActiveDataProvider('User', array(
'sort' => array(
'attributes' => array(
'user_id',
'email',
'averagerevenue'
),
'defaultOrder' => array(
'user_id' => CSort::SORT_ASC,
),
),
));
In view
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id' => 'user-grid',
'dataProvider' => $modelUser,
'columns' => array(
array(
'header' => 'User ID',
'name' => 'user_id',
),
array(
'header' => 'Email',
'name' => 'email',
),
array(
'header'=>'Average Revenue',
'value'=>'$data->averagerevenue',
),
)
));
?>
Sorting is applicable on user_id and email but Average Revenue column is not sortable. how to specify model method in sort() of CActiveDataprovider
Please help me to solve the problem.
Try this:
$modelUser = new CActiveDataProvider('User', array(
'sort' => array(
'attributes' => array(
'user_id',
'email',
'avgRevenue' //here's the change for you
),
'defaultOrder' => array(
'user_id' => CSort::SORT_ASC,
),
),
));
And your gridview column should be:
array(
'header'=>'Average Revenue',
'value'=>'avgRevenue',
),
and you can read more info on it over here:
http://www.yiiframework.com/wiki/167/understanding-virtual-attributes-and-get-set-methods/
Course HasMany Lesson.
I am trying to save a lesson in a Course view, but want to automatically assign the current course_id without them having to select it.
How do I do that?
Here is my Courses view controller:
public function view($id = null) {
if (!$this->Course->exists($id)) {
throw new NotFoundException(__('Invalid course'));
}
$options = array('conditions' => array('Course.' . $this->Course->primaryKey => $id));
$this->set('course', $this->Course->find('first', $options));
//Adding Lesson from Course View
if ($this->request->is('post')) {
$this->Course->Lesson->create();
if ($this->Course->Lesson->save($this->request->data)) {
$this->Session->setFlash(__('The lesson has been saved.'), array ('class' => 'alert alert-success'));
return $this->redirect(array('controller'=> 'lessons', 'action' => 'view', $this->Course->Lesson->id));
} else {
$this->Session->setFlash(__('The lesson could not be saved. Please, try again.'), array ('class' => 'alert alert-danger'));
}
}
$courses = $this->Course->Lesson->Course->find('list');
$this->set(compact('courses'));
}
Currently, it's saving the lesson but not passing the course_id to the new lesson.
Lesson Model:
public $belongsTo = array(
'Course' => array(
'className' => 'Course',
'foreignKey' => 'course_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Course Model:
public $hasMany = array(
'Lesson' => array(
'className' => 'Lesson',
'foreignKey' => 'course_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
This is basically a duplicate of CakePHP: Securely setting a default value on a form.
Check my answer there or read the blog article I've wrote after that answer.