Working with saveAssociated method in CakePHP - php

I'm trying to work with saveAssociated method in CakePHP without great success, the method seems to work partially, in facts I have three model in the save process:
Character, which $hasMany > Property and Label so this is the Character model:
class Character extends AppModel {
public $name = 'Character';
public $belongsTo = 'User';
public $hasMany = array (
'Property' => array (
'dependent' => true
)
);
public $hasOne = array (
'Label' => array (
'dependent' => true
)
);
public $validate = array (
'name' => array (
'required' => true,
'rule' => array('between', 0, 100),
'message' => 'This is the error message for "name" field'
),
'description' => array (
'allowEmpty' => true,
'rule' => array('between', 0, 100),
'message' => 'This is the error message for "description" field'
)
);
}
this is the data I get from the form:
array(
'Character' => array(
'name' => 'Character name',
'description' => 'Character description',
'image' => 'http://url.com/image.jpg'
),
'Label' => array(
'name' => 'Basic attributes',
'value' => 'Value'
),
'Property' => array(
(int) 0 => array(
'name' => 'Strenght',
'value' => '15'
)
)
)
then in my CharactersController i do this to saveAssociated data:
class CharactersController extends AppController {
public $uses = array ('Character', 'Property', 'Label');
public function add () {
if (!empty($this->request->data)) {
$this->Character->saveAssociated($this->request->data);
}
debug ($this->request->data);
}
}
The Character data is saved successfully but not Label and Property, where I'm wrong?

Have you declared the other ends of the Model Association in the other two Models? Have a good look at this page. Might make life easier if you use a $hasAndBelongsToMany association.

Try the saveAll method instead of saveAssociated.

Related

CakePHP - saving to more than two models using save associated

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

Creating a User who has-and-belongs-to-many existing Courses with a CakePHP form

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.

Silverstripe 3.1 - Export Dataobject with all relations?

I need to export the whole data of an dataobject. Database fields and relations.
private static $db = array (
'URLSegment' => 'Varchar(255)',
'SKU' => 'Text',
'Title' => 'Text',
'Price' => 'Text',
'Content' => 'HTMLText',
'ItemConfig' => 'Int',
'Priority' => 'Int'
);
private static $has_one = array (
'Visual' => 'Image'
);
private static $has_many = array (
'Sizes' => 'ShopItem_Size',
'Colors' => 'ShopItem_Color'
);
private static $many_many = array (
'Visuals' => 'Image',
'Categories' => 'ShopCategory'
);
I added everything to getExportFields(). But as expected the result for the relations is "ManyManyList" or "HasManyList"
public function getExportFields() {
return array(
'SKU' => 'SKU',
'Title' => 'Title',
'Price' => 'Price',
'Content' => 'Content',
'ItemConfig' => 'ItemConfig',
'Visual' => 'Visual',
'Visuals' => 'Visuals',
'Sizes' => 'Sizes',
'Colors' => 'Colors',
'Categories' => 'Categories'
);
}
Is it possible to create such an export?
Thank you in advance
You can use any method name instead of a field/relation name in the exportFields array.
In your ModelAdmin class
public function getExportFields() {
return array(
'SKU' => 'SKU',
'Title' => 'Title',
'CategoryNames' => 'Categories'
);
}
and just have a method with that name on the DataObject, returning the relation data as a string:
public function CategoryNames(){
$catNames = array();
foreach($this->Categories() as $cat){
$catNames[] = $cat->getField('Title');
}
//use a separator that won't break the CSV file
return join("; ", $catNames);
}
I think this is even way better than the modeladmin creating magic fields in your CSV file,
that would make it inconsistent...

Associating an item to multiple other items (of a different class) using Prestashop's backoffice

Having just arrived at Prestashop 1.5, I am making a very simple module: a video of the week, associated with multiple products that need to appear right next to it.
I decided to start from the Backoffice. Right now, I can view, add, edit and remove all the Video entries but I'm a bit lost on how to map the N-N association between a video and its related products... The lack of documentation isn't helping either.
Any ideas how to pull this off?
Here's a bit of my code, the Video class is defined by:
class Video extends ObjectModel {
public $id_video;
public $title;
public $url;
public $active;
public static $definition = array(
'table' => 'video',
'primary' => 'id_video',
'multilang' => false,
'fields' => array(
'id_video' => array(
'type' => ObjectModel :: TYPE_INT
),
'title' => array(
'type' => ObjectModel :: TYPE_STRING,
'required' => true
),
'url' => array(
'type' => ObjectModel :: TYPE_STRING,
'required' => true
),
'active' => array(
'type' => ObjectModel :: TYPE_BOOL,
'required' => true
)
),
);
(...)
and the AdminVideo class is here:
class AdminVideoController extends ModuleAdminController {
public function __construct()
{
$this->table = 'video';
$this->className = 'Video';
$this->lang = false;
$this->fields_list['id_video'] = array(
'title' => $this->l('ID'),
'align' => 'center',
);
$this->fields_list['title'] = array(
'title' => $this->l('Title'),
'width' => 'auto'
);
$this->fields_list['url'] = array(
'title' => $this->l('URL'),
'width' => 'auto'
);
$this->fields_list['active'] = array(
'title' => $this->l('Active'),
'width' => '70',
'align' => 'center',
'active' => 'status',
'type' => 'bool',
'orderby' => false
);
parent::__construct();
}
public function postProcess()
{
parent::postProcess();
}
public function renderList()
{
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->addRowAction('details');
return parent::renderList();
}
public function renderForm()
{
if (!($obj = $this->loadObject(true)))
return;
$this->fields_form = array(
'legend' => array(
'title' => $this->l('This weeks video'),
'image' => '../img/admin/world.gif'
),
'input' => array(
array(
'type' => 'text',
'label' => $this->l('Nome'),
'name' => 'title',
'size' => 33,
'required' => true,
'desc' => $this->l('Title')
),
array(
'type' => 'text',
'label' => $this->l('URL'),
'name' => 'url',
'size' => 33,
'required' => true,
'desc' => $this->l('Video URL')
),
array(
'type' => 'radio',
'label' => $this->l('Active:'),
'name' => 'active',
'required' => false,
'class' => 't',
'is_bool' => true,
'values' => array(
array(
'id' => 'active_on',
'value' => 1,
'label' => $this->l('Enabled')
),
array(
'id' => 'active_off',
'value' => 0,
'label' => $this->l('Disabled')
)
),
'desc' => $this->l('Only one video can be active at any given time')
),
)
);
if (Shop::isFeatureActive())
{
$this->fields_form['input'][] = array(
'type' => 'shop',
'label' => $this->l('Shop association:'),
'name' => 'checkBoxShopAsso',
);
}
$this->fields_form['submit'] = array(
'title' => $this->l(' Save '),
'class' => 'button'
);
if (!($obj = $this->loadObject(true)))
return;
return parent::renderForm();
}
}
One other thing: would it be possible to add a preview of the video inside the backoffice? I tried to echo YouTube's embed code, but it gets inserted even before the header. Is there a clean way of doing this or do I have to use some jQuery trickery? I was basically doing an echo of YT's embed code just before the end of postProcess().
Thanks in advance!
The simplest way to associate the videos to the products is by adding a "products" text field in your "video" table to store a comma separated list of the ids of the associated products (eg.: 1,10,27). Even if it's a bit rudimentary, it should work.
Alternatively, you could use a table like this:
create table video_product (
id_association int not null auto_increment,
id_video int,
id_product int,
primary key (id_association)
);
The problem with this solution is that the PrestaShop ObjectModel core does not provide any method to automatically update or delete the related tables (at least as far as I know), so you have to insert the code to manage the "video_product" table in your "Video" class.
If you want an example of how to do this, you should look at the classes/Product.php script, which manages the product table and all its related tables (categories, tags, features, attachments, etc.).
To have an idea of how the Prestashop database is structured, have a look at the docs/dbmodel.mwb file, which contains the schema of the database; this file can be viewed by using the MySQL Workbench application.

Working with Fieldset class and ORM in FuelPHP

First : sorry for my long message.
I'm trying to learn Fuel, but I have some problems with Fieldset class and Orm.
I've create a Model which extends ORM, in order to get an automatic generated form, according to my database.
My Model
class Model_Product extends \Orm\Model
{
protected static $_table_name = 'products';
protected static $_properties = array(
'id' => array(
'data_type' => 'int'
),
'name' => array(
'data_type' => 'varchar',
'label' => 'Name',
'validation' => array(
'required', 'trim', 'max_length'=>array(30), 'min_length'=>array(3)
),
),
'description' => array(
'data_type' => 'varchar',
'label' => 'Description',
'validation' => array(
'max_length' => array(290)
),
'form' => array(
'type' => 'textarea'
),
),
'price' => array(
'data_type' => 'integer',
'label' => 'Price',
'validation' => array(
'required', 'trim', 'valid_string' => array('numeric','dots')
),
),
'pic' => array(
'data_type' => 'varchar',
'label' => 'Path to the pic',
'validation' => array(
'required', 'trim'
),
),
'registered' => array(
'data_type' => 'date',
'label' => 'Registration date',
'validation' => array(
'required', 'trim'
) //'valid_string' => array('numeric','dashes')
),
);
} //end of class Model_Product
Then I create the controller which will validate the form.
My function from the controller
function action_add()
{
$fieldset = Fieldset::forge('add_product')->add_model('Model_Product')->repopulate();
$form = $fieldset->form();
$form->add('submit', '', array('type' => 'button', 'value' => 'Add item', 'class' => 'button-link' ));
$validation = $fieldset->Validation();
if($validation->run() === true)
{
$fields = $fieldset->validated();
//create a new Product, with validated fields
$product = new Model_Product;
$product->name = $fields['name'];
$product->description = $fields['description'];
$product->price = $fields['price'];
$product->pic = $fields['pic'];
$product->registered = $fields['registered'];
try
{
//if the product is successfully inserted in the database
if($product->save())
{
Session::set_flash('success', 'Product successfully added !');
\Response::redirect('products/product_details/'.$product->id);
}
}
catch(Exception $e)
{
Session::set_flash('error', 'Unable to save the product into the database !'.$e->getMessage());
}
}
//If the validation doesn't pass
else
{
Session::set_flash('error', $fieldset->show_errors());
}
$this->template->set('content', $form->build(), false);
} // end of method add()
My first question :
How and where in my function from controller can i add a 'fieldset' tag with a specific class, in order to 'beautify' my auto-generated form ?
Let's say
<fieldset class="add_product">
Second question :
What do I have to do in order to correctly validate de 'price' field, because in MySQL is set as decimal(5,2), but when I'm trying to validate with my actual validation rule, it doesn't pass (it works only with integer values Ex.: 42, but not with decimal Ex.: 42.35). I have tried to change the type from 'integer' to 'double', but it doesn't work .
If you can point to some specific documentation regarding my problems, which I possible didn't read yet, please do feel free.
Gabriel
I can answer the first question To change the automatically generated form you will need to copy fuel/core/config/form.php to the fuel/app/config directory and edit this file to suit your needs.

Categories