Cakephp 3.0 delete not working for same table association? - php

I have a category table with parent category Id in the same tables. And add the association to join the same table for classified child category. Below are my table structure, table association code and delete function.But delete not working :(.
Table Structure :-
CREATE TABLE IF NOT EXISTS `category` (
`Cat_Id` int(11) NOT NULL,
`Cat_Code` varchar(255) NOT NULL,
`Cat_Desc` text NOT NULL,
`CreatedBy` varchar(255) NOT NULL,
`ParentId` int(11) NOT NULL DEFAULT '0',
`GstPercentage` double NOT NULL DEFAULT '0',
`CreatedDate` datetime NOT NULL,
`UpdateDate` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `category`
--
ALTER TABLE `category`
ADD PRIMARY KEY (`Cat_Id`),
ADD UNIQUE KEY `Cat_id` (`Cat_Id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `category`
--
ALTER TABLE `category`
MODIFY `Cat_Id` int(11) NOT NULL AUTO_INCREMENT;
Model Association :-
public function initialize(array $config)
{
parent::initialize($config);
$this->table('category');
$this->displayField('Cat_Id');
$this->primaryKey('Cat_Id');
// association for the same table join
$this->belongsToMany('Parents', [
'className' => 'Category',
'joinTable' => 'category',
'foreignKey' => 'Cat_Id',
'targetForeignKey' => 'ParentId',
]);
// used to associate the table with user table (join)
$this->belongsTo('Users', [
'className' => 'Users',
'foreignKey' => 'CreatedBy',
'propertyName' => 'user'
]);
}
Delete Function :-
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$category = $this->Category->get(base64_decode($id));
if ($this->Category->delete($category)) {
$this->Flash->success(__('The category has been deleted.'));
} else {
$this->Flash->error(__('The category could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}

Related

Laravel Backpack: CRUD for one-many relation

I have a model called Report that has one-many relationship to another model called ReportAttachments.
In my ReportCrudController class, I have a the following field to add or manage attachments.
$this->crud->addField([ // Browse multiple
'name' => 'attachments',
'label' => 'Image Attachments',
'type' => 'browse_multiple',
'multiple' => true, // enable/disable the multiple selection functionality
'mime_types' => ['image'], // visible mime prefixes; ex. ['image'] or ['application/pdf']
'model' => 'App\Models\ReportAttachment',
'attribute' => 'id',
'entity' => 'attachments'
]);
However, I cant seem to make it work as I get this error during edit:
"Property [attachments] does not exist on this collection instance."
Also, when creating a new report, empty insert query is generated for the attachments which throws an exception:
"SQLSTATE[HY000]: General error: 1364 Field 'file' doesn't have a default value (SQL: insert into report_attachments () values ())"
This is the code for Report Model:
class Report extends Model
{
use CrudTrait;
protected $table = 'reports';
protected $fillable = ['name', 'slug', 'description', 'impression'];
protected $casts = [
'attachments' => 'array',
];
public function attachments()
{
return $this->hasMany('App\Models\ReportAttachment');
}
}
Code for Attachment Model
class ReportAttachment extends Model
{
protected $table = 'report_attachments';
protected $primaryKey = 'id';
protected $fillable = ['file', 'title', 'file_type'];
public $timestamps = false;
public function report()
{
return $this->belongsTo('App\Models\Report');
}
}
Am I missing something or doing something wrong here? Thanks in advance!
Update:
added table definition and updated namespace for model ReportAttachment
CREATE TABLE `reports` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`slug` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` longtext COLLATE utf8mb4_unicode_ci,
`impression` enum('0','1','2') COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `reports_name_unique` (`name`),
UNIQUE KEY `reports_slug_unique` (`slug`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
report_attachments table:
CREATE TABLE `report_attachments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT '',
`file` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`file_type` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`report_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `report_attachments_report_id_foreign` (`report_id`),
CONSTRAINT `report_attachments_report_id_foreign` FOREIGN KEY (`report_id`) REFERENCES `reports` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Saving data to 2 tables in the database in CakePHP

I have a simple sample project created to test how to add data to 2 tables from one controller in CakePHP, I have a tables called Student, Users, Admin and UserGroups. I need to add a Student to the database table and also add the students username and password to the users table. Here are the SQL for the tables.
CREATE TABLE `students` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_group_id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_group_id` int(11) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `user_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role` varchar(255) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
CREATE TABLE `admins` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_group_id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`post` varchar(255) DEFAULT NULL,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
I created the following function to add the data to the Students table and the Users table.
public function add() {
if ($this->request->is('post')) {
$userGrp = $this->request->data['Student']['user_group_id'];
$username = $this->request->data['Student']['username'];
$pass = $this->request->data['Student']['password'];
$this->Student->create();
if ($this->Student->save($this->request->data)) {
$this->request->data['User']['user_group_id'] = $userGrp;
$this->request->data['User']['username'] = $username;
$this->request->data['User']['password'] = $pass;
if ($this->Student->User->save($this->request->data)) {
$this->Session->setFlash(__('The student has been saved. Both in to Student and User'));
return $this->redirect(array('action' => 'index'));
}
} else {
$this->Session->setFlash(__('The student could not be saved. Please, try again.'));
}
}
$userGroups = $this->Student->UserGroup->find('list');
$this->set(compact('userGroups'));
}
The model for the Student is as follows.
<?php
App::uses('AppModel', 'Model');
class Student extends AppModel {
public $displayField = 'name';
public $belongsTo = array(
'UserGroup' => array(
'className' => 'UserGroup',
'foreignKey' => 'user_group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
When i add the Student details in to the database following error shows. Data is sent to the Student table but not to the Users table
Error: Call to a member function save() on a non-object
File: D:\wamp\www\MultiTables\app\Controller\StudentsController.php
What am i doing wrong here? please help.
IMO, students and admins should all be in a table called 'users'.
Your table that you named "users" is really a join table. That is an incorrect name for a join table if you are following conventions.
Then you would have these models:
Users (belongsTo UserGroups)
UserGroups (habtm Users via UserGroupUsers)
UserGroupUsers (belongsTo Users & belongsTo UserGroups)
If linked like that, then these inputs would save data to all three tables:
$this->Form->input('User.username')
$this->Form->input('User.password')
$this->Form->input('UserGroup.id', array('value' => 15)
You should check out CakePHP's "bake" feature. If you give it a properly constructed database, it will help you make correctly linked models.
Seems that what you're currently trying to do is:
Students (belongsTo Users)
UserGroups (habtm Students via Users & habtm Admins via Users)
Users (belongsTo Students & belongsTo Users & belongsTo Admins)
$this->Form->input('Student.username')
$this->Form->input('User.password')
$this->Form->input('UserGroup.id', array('value' => 15)
Model Student is not associated with model User.
So when you do $this->Student->User->save() with no association defined in the Student Model, You will afterwards get the error you showed because Student does not know about User.
Try defining a model Association between Student and User Model like this: A student belongs to the User and a User belongs to a UserGroup
class Student extends AppModel {
public $displayField = 'name';
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
class User extends AppModel {
public $belongsTo = array(
'UserGroup' => array(
'className' => 'UserGroup',
'foreignKey' => 'user_group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
This assumes then that you rename your Student.user_group_id into a Student.user_id in your database. Now if you want to save the User from the Student controller you can either use:
$this->Student->User->save($this->request->data)
or use the saveAssociated() method like this $this->Student->saveAssociated($this->request->data) if you named your form fields correctly

Can't figure out how to associate tables with php active record

I must be missing something important but I can't sort this thing out
I would like to associate many addresses to a person so ... here we go with my settlement:
Is there anyone to help?
CREATE TABLE `testpersons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
CREATE TABLE `testaddresses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
CREATE TABLE `testassociate` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`testuser_id` int(11) NOT NULL,
`testgroup_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
Then I create my objects:
class Testperson extends ActiveRecord\Model {
static $table_name = 'testpersons';
static $has_many = array(
array('testaddress',
'through'=>'test_associate',
'foreign_key'=>'testgroup_id',
'primary_key'=>'testuser_id',
'class_name'=>'Testaddress')
);
}
class Testaddress extends ActiveRecord\Model {
static $table_name = 'addresses';
static $belongs_to = array(
array('testperson',
'through'=>'test_associate',
'foreign_key'=>'testuser_id',
'primary_key'=>'testgroup_id')
);
}
Then trying to get my result:
$person = Testperson::find(2);
echo var_dump ( $person);
Gives that:
object(Testperson)[16]
public 'errors' => null
private 'attributes' (ActiveRecord\Model) =>
array (size=2)
'id' => int 2
'name' => string 'tata' (length=4)
private '__dirty' (ActiveRecord\Model) =>
array (size=0)
empty
private '__readonly' (ActiveRecord\Model) => boolean false
private '__relationships' (ActiveRecord\Model) =>
array (size=0)
empty
private '__new_record' (ActiveRecord\Model) => boolean false
Could someone tell me what's wrong with my association?
Many thx
If all you want to do is associate many Address to one Person, than your db structure is wrong and unnecessarily complex.
What you want is this:
CREATE TABLE `testpeople` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `testaddresses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`testperson_id` int(11) NOT NULL,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
);
Now your model would be like this:
class Testaddress extends ActiveRecord\Model {
static $belongs_to = array(
array('testperson')
);
}
class Testperson extends ActiveRecord\Model {
static $table_name = 'testpeople';
static $has_many = array(
array('testaddress')
);
}
That should be enough.
However, when you want to use through, you should have also a model for the intermediate table with the respective relationship.
For example sake, I'll use your db structure.
class Testperson extends ActiveRecord\Model {
static $table_name = 'testpeople';
static $has_many = array(
array('testassociate', 'foreign_key' => 'testuser_id'),
array('testaddress', 'through' => 'testassociate', 'foreign_key' => 'testgroup_id')
);
}
class Testassociate extends ActiveRecord\Model {
static $belongs_to = array(
array('testperson', 'foreign_key' => 'testuser_id'),
array('testaddress', 'foreign_key' => 'testgroup_id'),
);
}
class Testaddress extends ActiveRecord\Model {
static $has_one = array(
array('testassociate', 'foreign_key' => 'testgroup_id'),
array('testaddress', 'through' => 'testassociate', 'foreign_key' => 'testuser_id'),
);
}
Regarding the empty __relathionships, it's because that's a cache variable that will be populated when you request some relation, such as $person->testaddresses.

FuelPHP ORM cannot insert related table

My database:
CREATE TABLE IF NOT EXISTS `ws_accounts` (
`account_id` int(11) NOT NULL AUTO_INCREMENT,
`account_username` varchar(255) DEFAULT NULL COMMENT 'username',
`account_email` varchar(255) DEFAULT NULL COMMENT 'email',
`account_salt` varchar(255) DEFAULT NULL COMMENT 'store salt for use when hashing password',
`account_password` tinytext COMMENT 'password',
`account_display_name` varchar(255) DEFAULT NULL COMMENT 'name for display on web to prevent show username.',
`account_firstname` varchar(255) DEFAULT NULL COMMENT 'first name',
`account_middlename` varchar(255) DEFAULT NULL COMMENT 'middle name',
`account_lastname` varchar(255) DEFAULT NULL COMMENT 'last name',
`account_birthdate` date DEFAULT NULL COMMENT 'birthdate store in date format (YYYY-mm-dd)',
`account_avatar` varchar(255) DEFAULT NULL COMMENT 'avatar file. refer from root web without http or domain',
`account_signature` text COMMENT 'signature. very useful in forum',
`account_timezone` varchar(30) NOT NULL DEFAULT 'Asia/Bangkok' COMMENT 'see timezone list here http://www.php.net/manual/en/timezones.php',
`account_language` varchar(10) DEFAULT NULL COMMENT 'framework language shortcode eg: en, th',
`account_create` bigint(20) DEFAULT NULL COMMENT 'timestamp of account create date',
`account_create_gmt` bigint(20) DEFAULT NULL COMMENT 'timestamp of account create date in gmt0',
`account_last_login` bigint(20) DEFAULT NULL COMMENT 'timestamp of last login date',
`account_last_login_gmt` bigint(20) DEFAULT NULL COMMENT 'timestamp of last login date in gmt0',
`account_status` int(1) NOT NULL DEFAULT '0' COMMENT '0=disable, 1=enable',
`account_status_text` varchar(255) DEFAULT NULL COMMENT 'status text for describe why disable.',
`account_new_email` varchar(255) DEFAULT NULL COMMENT 'store new email waiting for confirmation',
`account_new_password` varchar(255) DEFAULT NULL COMMENT 'store new password in reset password progress',
`account_confirm_code` varchar(255) DEFAULT NULL COMMENT 'confirmation code. use for confirm register, change email, reset password',
`account_confirm_code_since` bigint(20) DEFAULT NULL COMMENT 'confirm code generated since',
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='contain user account' AUTO_INCREMENT=2 ;
--
-- Dumping data for table `ws_accounts`
--
INSERT INTO `ws_accounts` (`account_id`, `account_username`, `account_email`, `account_salt`, `account_password`, `account_display_name`, `account_firstname`, `account_middlename`, `account_lastname`, `account_birthdate`, `account_avatar`, `account_signature`, `account_timezone`, `account_language`, `account_create`, `account_create_gmt`, `account_last_login`, `account_last_login_gmt`, `account_status`, `account_status_text`, `account_new_email`, `account_new_password`, `account_confirm_code`, `account_confirm_code_since`) VALUES
(0, 'Guest', 'none#localhost', NULL, NULL, 'Guest', NULL, NULL, NULL, NULL, NULL, NULL, 'Asia/Bangkok', NULL, 1387121127, 1387095927, NULL, NULL, 0, 'This account is for guest actions.', NULL, NULL, NULL, NULL),
(1, 'admin', 'admin#localhost.com', NULL, '$P$FPnwJAQzX498tYCbbIfYTbdYiOCShE0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Asia/Bangkok', NULL, 1387121127, 1387095927, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL);
CREATE TABLE IF NOT EXISTS `ws_account_level` (
`level_id` int(11) NOT NULL AUTO_INCREMENT,
`level_group_id` int(11) NOT NULL,
`account_id` int(11) NOT NULL,
PRIMARY KEY (`level_id`),
KEY `level_group_id` (`level_group_id`),
KEY `account_id` (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `ws_account_level_group` (
`level_group_id` int(11) NOT NULL AUTO_INCREMENT,
`level_name` varchar(255) DEFAULT NULL,
`level_description` text,
`level_priority` int(5) NOT NULL DEFAULT '1' COMMENT 'lower is more higher priority',
PRIMARY KEY (`level_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='contain user role or level' AUTO_INCREMENT=5 ;
--
-- Dumping data for table `ws_account_level_group`
--
INSERT INTO `ws_account_level_group` (`level_group_id`, `level_name`, `level_description`, `level_priority`) VALUES
(1, 'Super administrator', 'For site owner or super administrator.', 1),
(2, 'Administrator', NULL, 2),
(3, 'Member', 'For registered user.', 999),
(4, 'Guest', 'For non register user.', 1000);
Models
class Model_Accounts extends \Orm\Model
{
protected static $_table_name = 'accounts';
protected static $_primary_key = array('account_id');
// relations
protected static $_has_many = array(
'account_level' => array(
'model_to' => 'Model_AccountLevel',
'key_from' => 'account_id',
'key_to' => 'account_id',
'cascade_delete' => true,
),
'account_fields' => array(
'model_to' => 'Model_AccountFields',
'key_from' => 'account_id',
'key_to' => 'account_id',
'cascade_delete' => true,
),
);
}
model/accounts.php
class Model_AccountLevelGroup extends \Orm\Model
{
protected static $_table_name = 'account_level_group';
protected static $_primary_key = array('level_group_id');
// relations
protected static $_has_many = array(
'account_level' => array(
'model_to' => 'Model_AccountLevel',
'key_from' => 'level_group_id',
'key_to' => 'level_group_id',
'cascade_delete' => true,
)
);
}
model/accountlevelgroup.php
class Model_AccountLevel extends \Orm\Model
{
protected static $_table_name = 'account_level';
protected static $_primary_key = array('level_id');
// relations
protected static $_belongs_to = array(
'account_level_group' => array(
'model_to' => 'Model_AccountLevelGroup',
'key_from' => 'level_group_id',
'key_to' => 'level_group_id',
),
'accounts' => array(
'model_to' => 'Model_Accounts',
'key_from' => 'account_id',
'key_to' => 'account_id',
)
);
}
model/accountlevel.php
Create user.
I use register method in accounts model to create user.
$data['account_username'] = 'unique_username';
$data['account_password'] = 'pass';
$data['account_email'] = 'no#email.tld';
$account = self::forge($data);
$account->account_level = new Model_AccountLevel();
$account->account_level->level_group_id = 3;
$account->save();
$account_id = $account->account_id;
and i got this error.
Assigned relationships must be an array or null, given relationship
value for account_level is invalid.
How to insert related table? What wrong with my code?
Your account_level relation is a has_many, this means that when accessing it you need to access it like an array. The ORM expects the value of this to be an array so assigning the model directly to it is invalid.
What you want to do is:
$account->account_level[] = new Model_AccountLevel(['level_group_id' => 3]);
(Using the array in the constructor because personally I find it's neater, but it's not required)

CakePHP does not save to the database

So I have three tables, the users, groups and users_groups which is a join table.
--
-- Table structure for table `groups`
--
CREATE TABLE `groups` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`description` text NOT NULL,
`all_versions_available` tinyint(1) unsigned NOT NULL DEFAULT '1',
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`,`created`,`modified`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=12 ;
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(40) NOT NULL,
`email` varchar(80) NOT NULL,
`password` varchar(50) NOT NULL,
`role` varchar(20) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`fullname` varchar(80) NOT NULL,
`password_token` varchar(40) NOT NULL,
PRIMARY KEY (`id`),
KEY `nickname` (`username`,`email`,`password`),
KEY `role` (`role`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
-- --------------------------------------------------------
--
-- Table structure for table `users_groups`
--
CREATE TABLE `users_groups` (
`user_id` int(11) unsigned NOT NULL,
`group_id` int(11) unsigned NOT NULL,
KEY `user_id` (`user_id`,`group_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Before I have implemented the HABTM in my Group and User models, the code I have below worked fine, now, I am getting all the data I need but I am unable to save.
So, my Group Model looks like this:
<?php
class Group extends AppModel {
public $hasAndBelongsToMany = array(
'Application' => array(
'className' => 'Application',
'joinTable' => 'applications_groups',
'foreignKey' => 'group_id',
'associationForeignKey' => 'application_id',
'unique' => 'keepExisting',
),
'User' => array(
'className' => 'User',
'joinTable' => 'users_groups',
'foreignKey' => 'group_id',
'associationForeignKey' => 'user_id',
'unique' => 'keepExisting',
)
);
public $validate = array(
'name' => array(
'required' => array(
'rule' => array('notEmpty'),
'message' => 'Group name is required'
)
)
);
public function saveGroup($id, $name, $description) {
$id = (int)$id;
if ($id) {
$this->id = $id;
}
else {
$this->create();
}
$this->set('name', $name);
$this->set('description', $description);
$this->save();
return $this;
}
public function getAll() {
$options = array('order' => array('Group.name' => 'ASC'));
$data = $this->find('all', $options);
return $data;
}
public function getOne($id) {
$id = (int)$id;
return $this->find('first', array('conditions' => array('Group.id' => $id)));
}
}
My User model looks like this:
<?php
class User extends AppModel {
public $hasAndBelongsToMany = array(
'Group' => array(
'className' => 'Group',
'joinTable' => 'users_groups',
'foreignKey' => 'group_id',
'associationForeignKey' => 'user_id',
'unique' => 'keepExisting',
)
);
public function getOne($id) {
$this->id = $id;
$data = $this->read(null, $id);
unset($data['User']['password']);
unset($data['User']['password_token']);
if (isset($data['User'])) $data['User']['gravatar_url'] = 'http://www.gravatar.com/avatar/'.md5($data['User']['email']).'.jpg';
return $data;
}
private function addGravatars($data) {
foreach ($data as $key=>$user) {
$data[$key]['User']['gravatar_url'] = 'http://www.gravatar.com/avatar/'.md5($user['User']['email']).'.jpg';
}
return $data;
}
public function getAll() {
$data = $this->find('all', array('order' => array('User.fullname' => 'ASC')));
$data = $this->addGravatars($data);
return $data;
}
public function countAll() {
return $this->find('count');
}
}
I have been using model for the join table:
<?php
class UsersGroup extends AppModel {
public function deleteAllWithGroup($groupId) {
$id = (int)$groupId;
return $this->deleteAll(array('UsersGroup.group_id' => $id), false);
}
public function saveUsersForGroup($users, $groupId=0) {
$this->deleteAllWithGroup($groupId);
$data = array();
foreach ($users as $id=>$user) {
$data[] = array('user_id'=>(int)$id, 'group_id'=>$groupId);
}
$this->saveMany($data);
}
}
And this is my Groups controller:
<?php
class GroupsController extends AppController {
var $uses = array('Group', 'User', 'UsersGroup');
public function index() {
$this->set('groups', $this->Group->getAllWithInfo());
}
public function edit($id=0) {
$this->set('group', $this->Group->getOne($id));
$this->set('usersList', $this->User->getAllWithGroupInfo($id));
if ($this->request->is('post')) {
$group = $this->Group->saveGroup($this->request->data['id'], $this->request->data['name'], $this->request->data['description']);
// Saving users
if (!isset($this->request->data['user']) || empty($this->request->data['user'])) {
$this->UsersGroup->deleteAllWithGroup($group->id);
}
else $this->UsersGroup->saveUsersForGroup($this->request->data['user'], $group->id);
}
}
public function view($id) {
App::uses('Platforms', 'Lib/Platform');
$this->setPageIcon('group');
$this->set('group', $this->Group->getOne($id));
}
public function delete($id) {
$this->Group->delete((int)$id);
return $this->redirect(array('action' => 'index'));
}
}
There is a couple of issues, the system above works if I remove the HABTM configs, second, I don't, for some very specific reasons not using the forms helper to generate the form and unfortunately for the complexity of the code (this is just a little bit) I can't so I have to name everything manually myself (that's where I see the biggest potential for failure) and lastly when I fire this code now I get:
Database Error
Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deleteAllWithGroup' at line 1
SQL Query: deleteAllWithGroup
Notice: If you want to customize this error message, create app/View/Errors/pdo_error.ctp
So the UsersGroup model is not being registered, nothing changes when I even delete the file, it is trying to use the name of the method I have previously used to delete the old join data as an SQL command. I have tried all the possible suggestions for naming and structure of the data I have found on Stack but failed, furthest I got was when I got only one of the join items to save, always the last one in the array ...
Anyone can help with this one?
Cheers,
O.
Be conventional
The main problems here seem to be caused by being unconventional
Table names
The docs describe the following:
This new join table’s name needs to include the names of both models involved, in alphabetical order, and separated with an underscore ( _ )
As such by default CakePHP will expect the join table for such a relationship to be called groups_users.
Model names
Given the above the join model for the relationship will be GroupsUser. Defining the hasAndBelongsToMany relationship as follows:
public $hasAndBelongsToMany = array(
'Group' => array(
'className' => 'Group',
'joinTable' => 'users_groups',
'foreignKey' => 'group_id',
'associationForeignKey' => 'user_id',
'unique' => 'keepExisting',
)
);
Means that CakePHP will still try and user a model named GroupsUser giving it the table name users_groups. To forcibly user a different join model it's necessary to define which model to use - with with:
public $hasAndBelongsToMany = array(
'Group' => array(
'className' => 'Group',
'joinTable' => 'users_groups',
'foreignKey' => 'group_id',
'associationForeignKey' => 'user_id',
'unique' => 'keepExisting',
'with' => 'UsersGroup'
)
);
Though it would be better to rename the join table and the join model, therefore the config could be reduced to the following, as everything else would be the defaults:
public $hasAndBelongsToMany = array(
'Group' => array(
'unique' => 'keepExisting'
)
);
Calls to a model function that don't exist becomes sql queries
Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deleteAllWithGroup' at line 1
SQL Query: deleteAllWithGroup
All this demonstrates, is that a query was made on a class which did not implement the called function. This can be verified by checking the class of the object:
debug($this->UsersGroup);
// Most likely "AppModel"
Note that the join model itself does not have any associations defined, as such doing this:
$this->UsersGroup->unbind(...);
Will have no effect - the associations are defined on the models User and Group in the question, even if the class UsersGroup were to be loaded - it does not have any associations defined, much less a habtm relationship to something else (which would require a total of 5 tables!)
Finally, and probably most importantly: this function isn't necessary:
HABTM data is treated like a complete set, each time a new data association is added the complete set of associated rows in database is dropped and created again
It won't cause problems to fix the code so the method is called except that the join table records are deleted whether the save succeeds or not with the code in the question; whereas CakePHP's logic will only delete the join table records upon success.
Be wary of creating thin wrapper functions
While there's nothing wrong with creating methods on your models to encapsulate logic - if that logic is very easily expressed using the existing model api all that does is make the code harder for others to read/debug. Code like this:
public function getOne($id) {
$this->id = $id;
$data = $this->read(null, $id);
unset($data['User']['password']);
unset($data['User']['password_token']);
if (isset($data['User'])) $data['User']['gravatar_url'] = 'http://www.gravatar.com/avatar/'.md5($data['User']['email']).'.jpg';
return $data;
}
Can easily be replaced with a find('first') call and adding a afterFind filter to the User model to add gravatar_url keys to returned results. This leads to less and simpler code.

Categories