cakephp HABTM same model - php

I have a site develop in cakephp 2.0
I want to make a HABTM relation to the same model: A product can has more products.
I thinked to do in this mode into my model:
class Product extends AppModel {
public $name = 'Product';
public $useTable = 'products';
public $belongsTo = 'User';
public $actsAs = array('Containable');
public $hasAndBelongsToMany = array(
'Ingredient' => array(
'className' => 'Product',
'joinTable' => 'ingredients_products',
'foreignKey' => 'product_id',
'associationForeignKey' => 'ingredient_id',
'unique' => true
)
);
}
Is correct my relation? But have I to insert into my table products the field product_id and ingredient_id?
And how can I save my data with a form? I know how to save data with HABTM but I never done an HABTM to the same table.

Your relation is fine. What you have written will create a Product Model that can have any number of Ingredients and allows an Ingredient to belong to any number of Products.
When saving, you must simply treat the Ingredient as if it were another Model. The CakePHP example on saving HABTM works just as well as for associating the same model as with 2 different models: http://book.cakephp.org/2.0/en/models/saving-your-data.html.
So, if you're saving multiple Ingredients to a Product, your Array structure will look like this:
Array(
[0] => Array(
Product => Array(
id => 1
),
Ingredient => Array(
id => 18
)
),
1 => Array(
Product => Array(
id => 1
),
Ingredient => Array(
id => 23
)
)
// ...
)
It is up to you how you capture this in a form, but the form example used in the link provided above should manage this properly.

Related

How To Save Data in CakePHP 2 for 3 tree like connected tables

I have 3 tree like connected tables. Their schemas as follows:
Member{
//Some column
}
Transactions{
member_id :: foreign key of member table
//Some other column
}
TransactionItems{
transaction_id :: foreign key of Transaction table
//Some other column
}
I define models like this:
class Members extends AppModel {
public $primaryKey = 'id';
public $hasOne = array(
'Transactions' => array(
'className' => 'Transactions',
'foreignKey' => 'member_id',
'dependent' => true
)
);
}
class Transactions extends AppModel {
public $primaryKey = 'id';
public $belongTo = array('Members');
public $hasOne = array(
'TransactionItems' => array(
'className' => 'TransactionItems',
'foreignKey' => 'transaction_id',
'dependent' => true
)
);
}
class TransactionItems extends AppModel {
public $primaryKey = 'id';
public $belongTo = array('Transactions');
public $belongsTo = array(
'Transactions' => array(
'className' => 'Transactions',
'foreignKey' => 'transaction_id'
)
);
}
I have a Data Array which I want to save into database. My scheme is:
Array(
[Members] = [],//Array
[Transactions] = [],//Array
[TransactionItems] = []//Array
)
The problem is that whenever I run $this->Members->saveAll($data). It save data in Member and Transactions table. But do not create data in TransactionItems table. I want to save in all 3 tables at a time.
Any help would be grateful.
Second level (and above) associations must be nested, ie the data structure needs to be:
array(
'Members' => array(),
'Transactions' => array(
'TransactionItems' => array()
)
)
A bit awkward, but that's how it works in 2.x. You can always refer to the structure that is being returned when reading data, it needs to be the same when saving it.
Furthermore you must set the deep option to true in order to be able to save second level and above associations (by default only first level associations are being saved):
$this->Members->saveAll($data, array('deep' => true));
See also
Cookbook > Models > Saving Your Data > Model::saveAssociated()

CakePHP - Correct way to do hasMany through

I have 3 models
Categories:
class Category extends AppModel {
public $belongsTo = array(
'Parent' => array(
'className' => 'Category',
'foreignKey' => 'parent_id'
),
);
public $hasMany = array(
'Children' => array(
'className' => 'Category',
'foreignKey' => 'parent_id'
),
'UserCategoryMeta'
);
}
Users:
class User extends AppModel {
public $hasMany = array(
'UserCategoryMeta' => array(
'className' => 'UserCategoryMeta',
'foreignKey' => 'user_id',
),
);
}
UserCategoryMeta:
class UserCategoryMeta extends AppModel
{
public $belongsTo = array(
'User', 'Category'
);
}
What I need to do is have each user be able to choose many categories and for each of those associations I need the user to set search terms which is just 1 field in the DB.
So the UserCategoryMeta table looks like this:
id | user_id | category_id | search_terms
I've found a way which might work but it seems very hacky.
In the usercontroller I have:
$Categories = $this->User->Category->find('list');
Then in the add view I have the checkboxes:
echo $this->Form->input('Category.Category',array(
'type' => 'select',
'multiple' =>'checkbox',
'options' => $Categories,
));
Then the only way I could get each of those checkboxes to have a search terms input next to them is to do this in the add view:
foreach ($Categories as $key => $category){
echo '<input type="text" id="Category'.$key.'SearchTerms" name="data[Category][search_terms]['.$key.']"><br/>';
}
This produces what I want but obviously since I'm just creating random inputs when the form get's submitted it get's black holed. I have managed to get passed this but I know I'm doing it the wrong way and hopefully someone can help me do it the right way.
Also then once I have this data array in the controller im not sure how to save it to the database correctly.
Thanks in advance for any help!
Just do what you're doing, but in the repeat, use $this->Form->input instead of just manually writing the HTML.

CakePHP -> findall without has_many

Model box
public $hasMany = array('Ticket' => array(
'className' => 'Ticket',
'order' => 'Ticket.created DESC',
'foreign_key' => 'box_id'
));
Model Ticket
public $belongsTo = 'Box';
In BoxesController I get data from box table
$this->set('boxes', $this->Box->find('all'));
This function get all data from table Box and from table Ticket.
How can I get data only from one table, without join other tables?
$this->Box->recursive = -1;
$this->set('boxes', $this->Box->find('all'));
or
$this->Box->unbindModel(
array('hasMany' => array('Ticket'))
);
$this->set('boxes', $this->Box->find('all'));
More on recursive

Cakephp : left-right joins with non-conventioned tables

I'm using an existing database (I can't change it and its table names are not like cake conventions want it), and I'd like to do some left joins but can't do it properly :/
I've already defined my tables, giving them primary keys and the relations in the models.
Here is my problem :
Table Wysipage can have 0 to n wysipage_content, and 0 to n wysipage_menu.
an element from wysipage_content corresponds to 1 and only 1 Wysipage.
an element from wysipage_menu corresponds to 0 or 1 Wysipage.
I'd like to make a request who would give me a list of all the elements from Wysipages, with their eventuals contents and menus, all that in a single table, and by only one request.
Here are my tables definitions (I'm avoiding you the entire schema, just be aware there is a wp_id and a wp_name column) :
class Wysipage extends AppModel {
var $actsAs = array('Containable');
public $useTable = 'wysipage';
public $primaryKey = 'wp_id';
public $displayField = 'wp_name';
var $hasMany = array(
'un' => array(
'Wysipage_contenu' => array(
'className' => 'Wysipage_contenu',
'foreignKey' => 'wpc_wp_id',
)),
'deux' => array(
'Wysipage_menu' => array(
'className' => 'Wysipage_menu',
'foreignKey' => 'wpm_wp_id',
))
);
class Wysipage_contenu extends AppModel {
var $actsAs = array('Containable');
public $useTable = 'wysipage_contenu';
public $primaryKey = 'wpc_id';
public $displayField = 'wpc_h1';
public $belongsTo = array(
'Wysipage' => array(
'className' => 'Wysipage',
'foreignKey' => 'wp_id'
)
);
class Wysipage_menu extends AppModel {
var $actsAs = array('Containable');
public $useTable = 'wysipage_menu';
public $primaryKey = 'wm_id';
public $displayField = 'wm_lien';
public $belongsTo = array(
'Wysipage' => array(
'className' => 'Wysipage',
'foreignKey' => 'wm_wp_id'
)
);
And here is my code to try request (but failed) :
$this->loadModel('Wysipage_contenu');
$this->loadModel('Wysipage_menu');
$this->Wysipage->contain();
$mes_wysipages = $this->Wysipage->find('all', array('joins' => array(
array(
'table' => 'wysipage_contenu',
'alias' => 'wpc',
'type' => 'LEFT',
'conditions'=> array('wpc.wpc_wp_id = Wysipage.wp_id')
),
array(
'table' => 'wysipage_menu',
'alias' => 'wpm',
'type' => 'LEFT',
'conditions'=> array('wpm.wm_wp_id = Wysipage.wp_id')
)
)));
$this->set('wysipages', $mes_wysipages);
$this->render();
What have I done wrong? Is the problem in my model declarations? Or do I use a wrong request type? :(
The request I'd like to make is simply :
SELECT wp_id, wp_name, wpc_id, wpc_name
FROM wysipage
LEFT JOIN wysipage_contenu ON wysipage.wp_id = wysipage_contenu.wpc_wp_id
Just this :(
I'm not even sure I want a LEFT join or a RIGHT join, but anyway the problem remains the same, this code gives me bad answers with multiple occurrences of the same lines :/
Thanks :/
PS : Sorry for my bad English, it's not my native language.
You can not join content and menu tables on wp_id in same query because they both have many-to-one relationship to wp_id, and there by for each row from content there are all rows from menu with same wp_id in result of such join. You need to do 2 queries: one for content and one for menu. Or if list columns you needed identical for both tables you can union both results from this two queries.

cakePHP bind hasmany through relationship

Ok this is kind of hard to explain but I'll try my best.
I have 3 tables
companies products product_availabilities
--------- -------- ----------------------
id id id
name name company_id
product_id
buys (tinyint)
sells (tinyint)
And their models
class Company extends AppModel
{
public $name = 'Company';
public $hasMany = array(
'ProductAvailability'
);
class Product extends AppModel
{
public $name = 'Product';
public $hasMany = array(
'ProductAvailability'
);
class ProductAvailability extends AppModel
{
public $name = 'ProductAvailability';
public $belongsTo = array(
'Company',
'Product'
);
}
What I want to do is when I create a company, I want to be able to select products that the company buys or sells. I've seen an example of a hasMany through relationship in the book (http://book.cakephp.org/1.3/view/1650/hasMany-through-The-Join-Model) but they are creating the form from the "join table" controller. Is it possible to bind the productAvailability model to my company model to be able to select the products while creating the company?
Edit : Here is how I've done it. I know it is not optimal as there is a lot of looping involved but it works.
Company controller :
$products = $this->Company->ProductAvailability->Product->find('list', array('fields' => array('Product.id', 'Product.label')));
$this->set('products', $products);
if($this->request->is('post')){
if($this->Company->save($this->request->data)){
foreach($products as $product)
{
$tmpArray = array(
'company_id' => $this->Company->id,
'product_id' => $product['Product']['id']
);
foreach($this->request->data('BuyProducts.product_id') as $buyProduct)
{
if($buyProduct == $product['Product']['id'])
$tmpArray['buys'] = 1;
}
foreach($this->request->data('SellProducts.product_id') as $sellProduct)
{
if($sellProduct == $product['Product']['id'])
$tmpArray['sells'] = 1;
}
if(count($tmpArray) > 2)
{
$this->Company->ProductAvailability->create();
$this->Company->ProductAvailability->set($tmpArray);
$this->Company->ProductAvailability->save();
}
}
$this->Session->setFlash('Yay', 'success');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('Nay', 'error');
}
}
Company add form :
<?php echo $this->Form->create('Company'); ?>
<?php echo $this->Form->input('name', array( 'div' => 'full-form')); ?>
<?php echo $this->Form->input('BuyProducts.product_id', array('multiple' => 'checkbox', 'options' => $products, 'div' => 'full-form', 'label' => false)); ?>
<?php echo $this->Form->input('SellProducts.product_id', array('multiple' => 'checkbox', 'options' => $products, 'div' => 'full-form', 'label' => false)); ?>
<?php echo $this->Form->end(array('label' => __('Save'), 'div' => 'center', 'class' => 'bouton-vert')); ?>
You have two options. Either let cakePHP do some magic with the hasAndBelongsToMany relationship or doing it manually which is necessary if you add attributes to the join table
1. CakePHP HABTM
Using the capabilities of CakePHP and making a straight forward solution I would make these changes:
Model
If one company has more than one product, and the products belong to many companies. It is a hasAndBelongsToMany relationship between Company<->Product
// company.php
...
var $hasAndBelongsToMany = array(
'Product' => array(
'className' => 'Product',
'joinTable' => 'companies_products',
'foreignKey' => 'company_id',
'associationForeignKey' => 'product_id',
'unique' => true,
)
);
...
// similar in product.php
Add the required table 'companies_products' in the database.
Controller
Then in the add function from the Company Controller there should be something like:
$products = $this->Company->Product->find('list');
$this->set(compact('products'));
View
Finally insert the products in the add.ctp, the select should allow multiple selections and let cakePHP do some magic, like this:
echo $this->Form->input('products', array(
'label' => 'Products to buy (Ctr+multiple choice)'
'type' => 'select',
'multiple' => true,
));
2. Manually
When the HABTM becomes more 'exotic' and includes some attributes like in your case 'buy' or 'sell' you need to do the manual way. This is in the Product Controller setting manually the fields before inserting them in the database. Something like:
foreach($availableProducts as $availableProduct ){
$this->Product->ProductAvailabilities->create();
$this->Product->ProductAvailabilities->set( array(
'company_id' => $this->Product->id,
'product_id' => $availableProduct['Product']['id'],
'buys' => $availableProduct['Product']['buy'],
'sells' => $availableProduct['Product']['sell']
// or however you send it to the controller
));
$this->Product->ProductAvailabilities->save();
}
Let's hope this helps you ...
you are planning a habtm-relationship (m:n) with the possibility to have extra fields in the join table. Even though this can be done with regular habtm I prefer the way you choose and implement the m:n as 1:n:1, which is simply the same and gives you more options when saving your data.
Here is a similar question and answer
As for your question: You can collect the the data from your products table like this:
$this->Company->ProductAvailability->Product->find('all', $params);
Also you might want to have a look at the containable-behaviour which is very useful for this use case:
$params['conditions'] = array(
'Company.id' => $id
);
$params['contain'] => array(
'ProductAvailability' => array(
'conditions' =>array(
'buys' => 1
),
'Product' => array(
'order' => array(
'name' => 'ASC'
)
)
)
);
$this->Company->find('all', $params);

Categories