CakePhp $hasAndBelongsToMany not saving multiple select items as expected - cakephp

I have the following code setup (snipped for brevity)
class BasePackage extends AppModel {
public $name = 'BasePackage';
public $hasAndBelongsToMany = array('ProductSubtype', 'ProductType');
}
class ProductType extends AppModel {
public $name = 'ProductType';
}
class ProductSubtype extends AppModel {
public $name = 'ProductSubtype';
}
Above are the simple Model classes.
/* tables in database */
base_packages
product_types
product_subtypes
base_packages_product_types
base_packages_product_subtypes
The first table is the main package that users are creating with the form, the product_* tables are pre-loaded with appropriate types and subtypes (they don't change very often), the last two are the Join tables that CakePhp wants to have
/* in BasePackage/add.ctp */
// ...
<ul class="nwblock">
<li>
<?php
echo $this->Form->input('ProductType.product_type_id', array(
'label' => 'Choose Product Type',
'type' => 'select',
'class' => 'form-control',
'style' => 'width:300px; margin-bottom:20px;',
'options' => $protypes
));
?>
</li>
</ul>
<ul class="nwblock">
<li>
<?php
echo $this->Form->input('ProductSubtype.product_subtype_id', array(
'label' => 'Choose Subtype(s)',
'multiple' => 'multiple',
'type' => 'select',
'class' => 'form-control',
'style' => 'width:300px;height:390px;margin-bottom:20px;',
'options' => $subtypes
));
?>
</li>
</ul>
// ...
Above we see the two controls that are loaded from the product_* tables. The types are a single select dropdown and the subtypes are a multiple select list.
/* in BasePackageController.php */
public function add() {
$protypes = $this->BasePackage->ProductType->find('list',
array('fields' => array('ProductType.id', 'ProductType.display')));
$subtypes = $this->BasePackage->ProductSubtype->find('list',
array('fields' => array('ProductSubtype.id', 'ProductSubtype.display')));
$this->set('protypes', $protypes);
$this->set('subtypes', $subtypes);
if ($this->request->is('post')) {
$this->BasePackage->create();
if (!empty($this->request->data)) {
$this->BasePackage->saveAll($this->request->data, array('deep' => true));
}
}
}
The process is as follows, while the user creates a new BasePackage, they select a ProductType from a dropdown box and one to many ProductSubtypes from a multiple select list. When the $this->BasePackage->saveAll() call is made, the data to be inserted into base_packages and base_packages_product_types tables is inserted correctly. However, the base_packages_product_subtypes table remains untouched.
UPDATE:
If I remove the 'multiple' => 'multiple', from the form->input options, the code saves both the producttype and the productsubtype (as expected). This is obviously not sufficient, as I need to save 1-to-many. Anyone know how to activate the 'Many' part of the HABTM?

To me BasePackage <> ProductType looks more like it should be a many-to-one relation, ie BasePackage belongsTo ProductType?
Anyways... please follow the conventions as described in the Cookbook:
http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-habtm
The form helper should be fed with the model name, ie ProductSubtype, and the view var should be camel backed plural, ie productSubtypes, that way CakePHP will do the rest for you automatically.
public function add() {
// ...
$this->set('productSubtypes', $subtypes);
// ...
}
echo $this->Form->input('ProductSubtype', array(
'label' => 'Choose Subtype(s)',
'class' => 'form-control',
'style' => 'width:300px;height:390px;margin-bottom:20px;'
));

Can you try with BasePackage->saveAssociated ?
http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array

Related

Can't submit form using create();

So i have this method inside of my:
JobsController.ctp:
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
/**
* Jobs Controller
*
* #property \App\Model\Table\JobsTable $Jobs
*/
class JobsController extends AppController
{
public $name = 'Jobs';
public function add()
{
//Some Vars assigning skipped, var job is empty
$this->set('job','Job');
$this->Job->create();
}
}
And I have this view with the form itself:
add.ctp:
<?= $this->Form->create($job); ?>
<fieldset>
<legend><?= __('Add Job Listing'); ?></legend>
<?php
echo $this->Form->input('title');
echo $this->Form->input('company_name');
echo $this->Form->input('category_id',array(
'type' => 'select',
'options' => $categories,
'empty' => 'Select Category'
));
echo $this->Form->input('type_id',array(
'type' => 'select',
'options' => $types,
'empty' => 'Select Type'
));
echo $this->Form->input('description', array('type' => 'textarea'));
echo $this->Form->input('city');
echo $this->Form->input('contact_email');
?>
</fieldset>
<?php
echo $this->Form->button('Add');
$this->Form->end();
?>
Also this table class:
JobsTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
class JobsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Types', [
'foreignKey' => 'type_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Categories', [
'foreignKey' => 'category_id',
'joinType' => 'INNER',
]);
}
}
And when I submit it, it gives me next error:
Error: Call to a member function create() on boolean
No idea how to fix.
I also have an entity
Job.php:
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
/**
* Job Entity.
*/
class Job extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* #var array
*/
protected $_accessible = array(
'category_id' => true,
'user_id' => true,
'type_id' => true,
'company_name' => true,
'title' => true,
'description' => true,
'city' => true,
'contact_email' => true,
'category' => true,
'user' => true,
'type' => true,
);
}
So how do I fix this error, that appears on form submit?
Error: Call to a member function create() on boolean
I guess I need to do something with $this->set('job'); ? but I'm not sure what exactly
By convention the default, auto-loadable table for a controller is based on the controller name without the trailing Controller, so for JobsController a table class named Jobs(Table) can be autoloaded.
In case the table class cannot be loaded (for example because it doesn't exist, or because the name doesn't match the one derived from the controller name), the magic getter that handles this will return false, a boolean, and this is where you are trying to call a method on, hence the error.
create() btw doesn't exist anymore, you should have a look at the ORM migration guide, and the docs in general to get a grasp on how things now work.
So either use $this->Jobs and make sure that you have a table class named JobsTable, or override the default model to use (Controller::_setModelClass()), or load the desired table manually (TableRegistry::get() or Controller::loadModel()).
See also
Cookbook > Database Access & ORM
Cookbook > Controllers > Loading Additional Models

CakePHP Access a different table my view

I have two tables Contact and Quote, this is a one to many relationship (i.e. one contact can have many quotes). Foreign keys are all setup correctly.
When I go to create a new quote I want to be able to select from a drop down list of contacts.
My code looks like this:
Contact Model:
class Contact extends AppModel {
public $hasMany = array('Quote' => array('className' => 'Quote', 'foreignKey' => 'contact_id'));
}
Quote Model
class Quote extends AppModel {
public $belongsTo = array('Contact' => array('className' => 'Contact', 'foreignKey' => 'contact_id'));
public $validate = array(
'name' => array(
'rule' => 'notEmpty'
),
'amount' => array(
'rule' => 'notEmpty'
)
);
}
Add method in QuotesController:
public function add() {
// TODO: Update this so the user can select the id from a drop down list.
$this->request->data['Quote']['contact_id'] = '1';
if ($this->request->is('post')) {
$this->Quote->create(); // This line writes the details to the database.
if ($this->Quote->save($this->request->data)) {
$this->Session->setFlash('Your quote has been saved.');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('Unable to add your quote.');
}
}
}
As you can see I'm currently just hard coding the user id as part of the add process.
I'm assuming you have read and are using this method in your view.
http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::select
Less easy to find (or easier to overlook) is this:
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#find-list
So in your controller you want to do
$contacts = $this->Article->find('list', array('fields' => array('Contact.id', 'Contact.name'));
$this->set(compact('contacts'));
Then in the view:
echo $this->Form->select('contact_id', $contacts);
Modify the fields for the find to reflect what is actually in your model. And if you need fields combined, you can possibly do it with virtual fields: http://book.cakephp.org/2.0/en/models/virtual-fields.html. Otherwise you can select the fields that need to be composited and use a foreach loop to combine them into a id=>[displayed value] array to pass on to the view. Only the id is the important thing and has to correspond to an id in the Contacts table.

CakePHP 2 Dynamic Tree Categories Menu

For CakePHP 2
I would like to create a categories menu which would list the categories of my products. It's a 3 levels menu. Each category in the menu is a link that opens a page listing all the products that belong to it. So if the category is a parent one, it should list all the products contained in the children, the 2 sub-levels. Also, if the category is a children, it should link to a listing page of the products that belong to it.
With that said, here's what I've done so far.
I created my categories table according to cake's conventions with the following columns:
id--parent_id--lft--rght--name
Then my products' table:
id--name--slug--category_id
Now the Category.php model:
<?php
class Category extends AppModel {
public $name = 'Category';
public $actsAs = array('Tree');
public $belongsTo = array(
'ParentCategory' => array(
'className' => 'Category',
'foreignKey' => 'parent_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
public $hasMany = array(
'ChildCategory' => array(
'className' => 'Category',
'foreignKey' => 'parent_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
I'm using the ProductsController to render the categories menu because this is the page that will hold this categories menu:
<?php
class ProductsController extends AppController{
public $uses = array('Product');
public function index(){
$this->layout = 'products';
$this->loadModel('Category');
$this->set('data',$this->Category->generateTreeList());
}
}
and my index.ctp view:
<?php debug($categories); ?>
What i would like now is to build a nested ul-li menu of my categories that link to the products page they belong according to the tree.
<ul class="ulclass">
<li class="liclass">category</li>
</ul>
I checked only for this kind of tutorial, unfortunately I didn't find anything well explained, I did find a TreeHelper but i have no idea how to use it >>> TreeHelper from Github
However, I would like to have the control on my category's tree menu by having the possibility to add css classes. If you think this helper can provide me this construction so it's fine then. But I have no idea how to use it though. And not to mention that I'm new to CakePHP :( but I want to learn it because it's a great tool.
I forgot something about my DB, do I have to add any other column in my tables to make this system work or is it correct as is?
Last thing, as I didn't find anything for CakePHP 2 about this categories/products dynamic tree menu, I will share the entire code on Github so that it can help many others.
All right.
Assuming you use my updated version:
// in your controller
$categories = $this->Model->children($id);
// or
$categories = $this->Model->find('threaded', array(...));
Then pass it down to the view.
// in your view ctp
$categories = Hash::nest($categories); // optional, if you used find(all) instead of find(threaded) or children()
$treeOptions = array('id' => 'main-navigation', 'model' => 'Category', 'treePath' => $treePath, 'hideUnrelated' => true, 'element' => 'node', 'autoPath' => array($currentCategory['Category']['lft'], $currentCategory['Category']['rght']));
echo $this->Tree->generate($categories, $treeOptions);
And here an example of the element in /Elements/node.ctp:
$category = $data['Category'];
if (!$category['active'] || !empty($category['hide'])) { // You can do anything here depending on the record content
return;
}
echo $this->Html->link($category['name'], array('action' => 'find', 'category_id' => $category['id']));
Here is a simple solution, Used in controller for index view. Later you use it by two for each loops for each $posts as $post and foreach $post['Post']['children'].
$posts = $this->Post->find('all', array('conditions' => array('parent_id'=>null)));
foreach ($posts as $postKey => $post) {
$posts[$postKey]['Post']['children'] = $this->Post->find('all', array('conditions' => array('parent_id'=>$post['Post']['id'])));
}
$this->set('posts', $posts);

Not sure how to retrieve my the values from array

I have two issues with my CakePHP application (its my first one in CakePHP). I am trying to convert an old php website to cake.
1.Issue
I have my controller that accepts a parameter $id, but the data is comming from joined tables so in the cookbook it had something like this
MY Controller
dish_categories_controller.php
class DishCategoriesController extends AppController {
var $uses = array("DishCategory");
var $hasOne ='';
function get_categories($id)
{
$this->set('dishes',$this->DishCategory->find());
$this->layout = 'master_layout';
}
}
model
dish_category.php
class DishCategory extends AppModel{
var $name = 'DishCategory';
var $hasOne = array(
'Dish' => array(
'className' => 'Dish',
'conditions' => array('Dish.id' => '1'),
'dependent' => true
)
);
}
As you can see the Dish.id=> '1' is hard coded, how can make it dynamic there so that I pass a value and make it something like Dish.if =>$id ?.
So that was my first issue.
The second issue is related to the view
That model returns only one record, how can I make it so that it returns all and also how would I be able to loop through that, below the code in the view currently and the array format.
This is in my view
<?php
echo $dishes['DishCategory']['category_info'];
echo $dishes['DishCategory']['category_title'];
echo $dishes['Dish']['dish_name'];
echo $dishes['Dish']['dish_image'];
echo $this->Html->image($dishes['Dish']['dish_image'], array('alt' => 'CakePHP'))
?>
Array Format
Array ( [DishCategory] => Array
( [id] => 1 [category_name] => Appetizers
[category_keywords] => appetizer, appetizers
[category_title] => Our Side Dishes
[category_info] => Test Test
[dish_id] => 1 )
[Dish] => Array ( [id] => 1
[dish_name] => Rice
[dish_disc] => The Best flavor ever
[dish_price] => 2.90 [dish_image] => /img/rice_chicken.jpeg [dish_category_id] => 1
[dish_price_label] => Delicious Arepa ) )
I would appreciate your help to help me understand how to better do this. Thank you.
Firstly, you DishCategoriesController has model properties, you can remove them. In your controller, you will set up the conditions for the find like so:
class DishCategoriesController extends AppController {
function get_categories($id)
{
// find category with a dish of $id
$this->set('dishes', $this->DishCategory->find('all', array(
'conditions' => array(
'Dish.id' => $id
)
)));
// set master layout
$this->layout = 'master_layout';
}
}
Your DishCategory model will look very basic, you don't need to hard code the relationship condition:
class DishCategory extends AppModel {
/**
* hasOne associations
*
* #var array
*/
public $hasOne = array(
'Dish' => array(
'className' => 'Dish',
'foreignKey' => 'dish_category_id'
)
)
}
At this point it is worth noting that since the DishCategory hasOne Dish, using the above find query, it will only ever return a single result. But, it you were returning multiple results, you could loop through them in your view like so:
<?php foreach ($dishes as $key => $dish): ?>
<?php var_dump($dish) ?>
<?php endforeach ?>

Cakephp: BelongsTo Relationship

I want to model the following simple relationship:
One Passenger belongs to a Car; One Car has many Passengers.
The passenger table has an id and Car_id column, the Car table has one id column.
My models look like this:
<?php
class Passenger extends AppModel {
var $name = 'Passenger';
var $belongsTo = 'Car';
} ?>
and
<?php
class Car extends AppModel {
var $name = 'Car';
var $hasMany = array (
'Passenger' => array (
'className' => 'Passenger',
'foreignKey' => 'car_id'
)
);
}
?>
and my add Passenger .ctp looks like this:
<?php
echo $this->Form->create('Passenger');
echo $this->Form->input('car_id');
echo $this->Form->end('Save');
?>
BUt when I access the page to add a passenger, all I see is an empty drop down box. Is there an additional step I must take in order to populate the dropbox with all cars?
First off, you have forgotten to mention the belongsTo relation in your Passenger model:
<?php
class Passenger extends AppModel {
var $name = 'Passenger';
var $belongsTo = array('Car');
}
?>
Next, in the corresponding action of your controller, you will need to obtain a list of all the cars from the database, and set it to the plural form of the model's variable ($cars). You would do that like so:
$cars = $this->Passenger->Car->find('list');
$this->set(compact('cars'));
This will convert the car_id input field into a drop down list with the populated information.
HTH.
The Passenger will only know about the car with which it is associated - at this point, none.
In the add method in the passenger controller, do
$this->Car->find('list');
and pass the result into your view:
$this->set('cars',$cars);
In the view, give the $cars variable as the value for $options in the field declaration:
echo $this->Form->input('car_id', array('options' => $cars));
Alternatively, you can do something like:
echo $this->Form->input('Car.id', array('options' => $cars));
$this->CompanyCashback->bindModel(array('belongsTo' => array(
'CompanyBranch' => array('className' => 'CompanyBranch', 'foreignKey' => false, 'conditions' => array('CompanyCashback.publisher_id = CompanyBranch.publisher_id && CompanyBranch.branch_type = "online" ')),
'PersonalInformation' => array('className' => 'PersonalInformation', 'foreignKey' => false, 'conditions' => array('CompanyCashback.publisher_id = PersonalInformation.user_id')),
'Country' => array('className' => 'Country', 'foreignKey' => false, 'conditions' => array('PersonalInformation.country_id = Country.id')),
'User' => array('className' => 'User', 'foreignKey' => false, 'conditions' => array('PersonalInformation.user_id = User.id')))
));

Resources