We used Yii2 framework last alpha. Role for user already created but problem is how it assign to user. Documentation is absent.
For database version of RBAC use DbManager (quote frm: Alexufo):
use yii\rbac\DbManager;
$r=new DbManager;
$r->init();
$r->createRole("admin","Administrator");
$r->save();
$r->assign('1','admin'); //1 is user id
Example Access rules:
<?php
namespace backend\controllers;
use yii;
use yii\web\AccessControl;
use yii\web\Controller;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
//'actions' => ['login', 'error'], // Define specific actions
'allow' => true, // Has access
'roles' => ['#'], // '#' All logged in users / or your access role e.g. 'admin', 'user'
],
[
'allow' => false, // Do not have access
'roles'=>['?'], // Guests '?'
],
],
],
];
}
public function actionIndex()
{
return $this->render( 'index' );
}
}
?>
Don't forget to add this to your configuration file (config/main.php):
'components' => [
'authManager'=>array(
'class' => 'yii\rbac\DbManager',
'defaultRoles' => ['end-user'],
),
...
]
Tables:
drop table if exists `tbl_auth_assignment`;
drop table if exists `tbl_auth_item_child`;
drop table if exists `tbl_auth_item`;
create table `tbl_auth_item`
(
`name` varchar(64) not null,
`type` integer not null,
`description` text,
`biz_rule` text,
`data` text,
primary key (`name`),
key `type` (`type`)
) engine InnoDB;
create table `tbl_auth_item_child`
(
`parent` varchar(64) not null,
`child` varchar(64) not null,
primary key (`parent`,`child`),
foreign key (`parent`) references `tbl_auth_item` (`name`) on delete cascade on update cascade,
foreign key (`child`) references `tbl_auth_item` (`name`) on delete cascade on update cascade
) engine InnoDB;
create table `tbl_auth_assignment`
(
`item_name` varchar(64) not null,
`user_id` varchar(64) not null,
`biz_rule` text,
`data` text,
primary key (`item_name`,`user_id`),
foreign key (`item_name`) references `tbl_auth_item` (`name`) on delete cascade on update cascade
) engine InnoDB;
You can also find this information in the "yii/rbac" directory (including other SQL files).
For functionality and more details:
https://github.com/yiisoft/yii2/blob/master/docs/guide/security-authorization.md
$user_id = 1;
$auth = new DbManager;
$auth->init();
$role = $auth->createRole('editor');
$auth->add($role);
$auth->assign($role, $user_id);
=========================================================================
if you want to select role instead creating then
$auth = new DbManager;
$auth->init();
$role = $auth->getRole('admin');
$auth->assign($role, $user_id);
100% worked!
Solved!
================ create role ============
use yii\rbac\PhpManager;
$r=new PhpManager;
$r->init();
$r->createRole("admin","Администратор");
$r->save();
=============== assign ==================
$r->assign('1','admin'); //1 is user id
A really simple way to achieve an admin role is to add this to your controller:
use yii;
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'actions' => ['index'],
'roles' => ['#'],
],
[
'allow' => !Yii::$app->user->isGuest && Yii::$app->user->identity->isAdmin(),
'actions' => ['view', 'create', 'update', 'delete'],
],
],
],
];
}
Then add to your User model an isAdmin() which returns true for your admin user(s) and false for everyone else. Personally, I use:
public function isAdmin() {
return Self::ROLE_ADMIN === $this->role;
}
Admittedly, this is not "by the book". But it is simple, quick and effective.
$user_id = \Yii::$app->user->id;
$auth = new DbManager;
$auth->init();
$role = $auth->createRole('editor');
$auth->add($role);
$auth->assign($role, $user_id);
Related
I have a table named Userprofile in the backend which saves the user information including profile image Yii::$app->params['uploadPath'] = Yii::$app->basePath . '/web/avatar/'; but when i tried to used the same table from the frontend then the image is saved in the frontend as i have used basePath. How can i save the profile pic in same folder and can do CRUD operation from frontend as well as backend?
If you need simply render the related views using alias eg:
if you want use a backend view from front end
class YourControllerController extends Controller
{
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['post'],
],
],
];
}
public function actionIndex()
{
$searchModel = new YourModelSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider->pagination->pageSize=15;
return $this->render('#backend/views/your-controller/index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
suppose you want to save all images for (frontend and backend) CRUD under frontend/web/uploads
so in your backend config file add this component:
'urlManagerFrontEnd' => [
'class' => 'yii\web\urlManager',
'baseUrl' => 'http://front.domain.ext/',//frontend app url
],
then use it from the backend controllers or models :
Yii::$app->urlManagerFrontEnd->baseUrl ."/path/to/your/place";
Hope this help
configuration with migration . folder path: common\config\main-local.php
create table: client
CREATE TABLE `client` (
`client_id` int NOT NULL AUTO_INCREMENT ,
`first_name` varchar(255) NOT NULL ,
`middle_name` varchar(255) NULL ,
`last_name` varchar(255) NOT NULL ,
PRIMARY KEY (`client_id`)
);
go to backend gii example: http://backend.dev/gii
I am creating a yii application and I want to insert data into the database but the problem is how to insert date and time. It fails to insert every detail until I remove the date time column from the database .
This is the controller class:
<?php
namespace app\controllers;
use amnah\yii2\user\models\User;
use app\models\Influence;
use Yii;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use amnah\yii2\user\controllers\DefaultController as SuperDefault;
class DefaultController extends SuperDefault {
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'actions' => ['index', 'confirm', 'resend', 'logout'],
'allow' => true,
'roles' => ['?', '#'],
],
[
'actions' => ['account', 'profile','contact', 'resend-change', 'cancel','advertiser_dashboard','influencer_dashboard', 'influenza_info', 'advertiser'],
'allow' => true,
'roles' => ['#'],
],
[
'actions' => ['login', 'register','contact','influencer_dashboard', 'advertiser_dashboard','register_advert','forgot', 'reset', 'login-email', 'login-callback'],
'allow' => true,
'roles' => ['?'],
],
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['post', 'get'],
],
],
];
}
public function actionInfluenza_info()
{
$u_id = Yii::$app->user->id;
$user = User::findOne($u_id);
$influence= new Influence();
if ($influence->load(Yii::$app->request->post())) {
$influence_data = Yii::$app->request->post('Influence', []);
$influence->firstname = $influence_data['firstname'];
$influence->lastname = $influence_data['lastname'];
$influence->email = $influence_data['email'];
$influence->city = $influence_data['city'];
$influence->type = $influence_data['type'];
$influence->pic = $influence_data['pic'];
$influence->address = $influence_data['address'];
$influence->country = $influence_data['country'];
$influence->gender = $influence_data['gender'];
$influence->id = $u_id;
$influence->save();
Yii::$app->session->setFlash("Profile-success", Yii::t("user", "Profile updated"));
return $this->refresh();
} else {
return $this->render('influenza_info', [
'user' => $user,
'influence' =>$influence
]);
}
}
}
this is the model class
<?php
namespace app\models;
use Yii;
use app\controllers\Expression;
/**
* This is the model class for table "influence".
*
* #property integer $id
* #property integer $user_id
* #property string $firstname
* #property string $lastname
* #property string $email
* #property string $city
* #property string $type
* #property string $pic
* #property string $address
* #property string $country
* #property string $gender
*/
class Influence extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'influence';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['id', 'user_id', 'firstname', 'lastname', 'email', 'city', 'type', 'pic', 'address', 'country', 'gender'], 'required'],
[['id', 'user_id'], 'integer'],
[['firstname', 'lastname', 'email', 'city', 'type', 'pic', 'address', 'country', 'gender'], 'string', 'max' => 30]
];
}
public function beforeSave($insert)
{
if ($this->isNewRecord) { // new record only, otherwise time is inserted every time this record is updated
$this->created_at = new Expression('NOW()');
}
parent::afterSave($insert);
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'user_id' => 'User ID',
'firstname' => 'Firstname',
'lastname' => 'Lastname',
'email' => 'Email',
'city' => 'City',
'type' => 'Type',
'pic' => 'Pic',
'address' => 'Address',
'country' => 'Country',
'gender' => 'Gender',
'created_at' => 'Created_at',
];
}
}
the table sql
CREATE TABLE `influence` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`firstname` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`lastname` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`email` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`city` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`pic` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`address` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`country` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`gender` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`created` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP
) ;
how do i make it insert automatically. if i try it from my admin it works and gets the dateand time . but with yii it does not.
To do this I remove the created attribute from the required rule.
I then create a beforeSave() method and on a new record insert it with Expression.
public function beforeSave($insert)
{
if ($this->isNewRecord) { // new record only, otherwise time is inserted every time this record is updated
$this->created = new Expression('NOW()');
}
parent::beforeSave($insert);
}
Make sure you import Expression into the model
Update
updated code thanks #crafter
you should use the Expression in the following way. Refer this link Class yii\db\Expression
When an Expression object is embedded within a SQL statement or fragment, it will be replaced with the $expression property value without any DB escaping or quoting. For example,
$expression = new Expression('NOW()');
$now = (new \yii\db\Query)->select($expression)->scalar(); // SELECT NOW();
echo $now; // prints the current date
The issue is that you are confusing the beforeSave and afterSave events.
In addition, your field name is created, not created at.
public function beforeSave($insert)
{
if ($this->isNewRecord) { // new record only, otherwise time is inserted every time this record is updated
$this->created = new Expression('NOW()'); // <== You were setting $this->created_at
}
parent::beforeSave($insert); // <== You were calling afterSave()
}
You have
`created` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP
in you db table but seems you have model field created as a string.
* #property string $created
check if there is mismatch problem related to ths field and eventually provide the right conversion before save or change the model (or the db) in order to have the same data type ..
Anyway you don't have rules for $created and the is not POSTED
add
public function rules()
{
return [
[['id', 'user_id', 'firstname', 'lastname', 'email', 'city', 'type', 'pic', 'address', 'country', 'gender'], 'required'],
[['id', 'user_id'], 'integer'],
[['firstname', 'lastname', 'email', 'city', 'type', 'pic', 'address', 'country', 'gender'], 'string', 'max' => 30]
[['created',], 'safe'],
];
}
Try also
$influence->save(false);
in this way you save the data withot validation check (use this only for debugging) if in this way the model is saved the try addding
[['created'], 'date'],
in rules
UPDATE
So I added some logs to the action upload in ProductsController and the method upload in MediasTable to find out what is happening. The entity from ProductsController this->Products->Medias->newEntity() was pass to MediasTable but it wasn't save.
It is necessary to upload the file to save the data in the db? Like if all the data is ok but the file is no present the event will be reject the data and do nothing in the db?
I'm using cakephp 3.1 with the file-storage plugin. I'm following the quickstart guide from the docs: Quick-Start but I don't understand some parts and doesn't upload, insert in database neither make thumbnails.
This is my database:
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(255) NOT NULL,
quantity INT NOT NULL,
sold INT NOT NULL,
description VARCHAR(1000),
price DECIMAL(7,2) NOT NULL,
old_price DECIMAL(7,2) NOT NULL,
visited INT NOT NULL,
status INT NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE media_types (
id INT AUTO_INCREMENT PRIMARY KEY,
name_media_type VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE medias (
id INT AUTO_INCREMENT PRIMARY KEY,
media_type_id INT NOT NULL,
product_id INT NOT NULL,
path VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME,
FOREIGN KEY media_type_key (media_type_id) REFERENCES media_types(id),
FOREIGN KEY product_key (product_id) REFERENCES products(id)
);
MediasTable:
...
use Burzum\FileStorage\Model\Table\ImageStorageTable;
class MediasTable extends ImageStorageTable {
public function initialize(array $config) {
parent::initialize($config);
$this->table('medias');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('MediaTypes', [
'foreignKey' => 'media_type_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Products', [
'foreignKey' => 'product_id',
'joinType' => 'INNER'
]);
}
...
public function upload($productId, $entity) {
$media = $this->patchEntity($entity, [
'adapter' => 'Local',
'model' => 'Media',
'foreign_key' => $productId
]);
Log::write('debug', $media);
return $this->save($media);
}
}
ProductsTable:
class ProductsTable extends Table {
public function initialize(array $config) {
parent::initialize($config);
$this->table('products');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Medias', [
'className' => 'Medias',
'foreignKey' => 'foreign_key',
'conditions' => [
'Medias.model' => 'Media'
]
]);
}
...
}
ProductsController:
class ProductsController extends AppController {
public function upload($productId = null) {
$productId = 2;
$entity = $this->Products->Medias->newEntity();
if ($this->request->is(['post', 'put'])) {
$entity = $this->Products->Medias->patchEntity(
$entity,
$this->request->data
);
if ($this->Products->Medias->upload($productId, $entity)) {
$this->Flash->set(__('Upload successful!'));
}
}
$this->set('productImage', $entity);
}
...
}
In config/local_store.php is the same as the example (I include this file in boostrap.php)
...
$listener = new LocalFileStorageListener();
EventManager::instance()->on($listener);
$listener = new ImageProcessingListener();
EventManager::instance()->on($listener);
Configure::write('FileStorage', [
'imageSizes' => [
'Medias' => [
'large' => [
...
]);
FileStorageUtils::generateHashes();
StorageManager::config('Local', [
'adapterClass' => '\Gaufrette\Adapter\Local',
'adapterOptions' => [TMP, true],
'class' => '\Gaufrette\Filesystem'
]);
upload.ctp
echo $this->Form->create($productImage, array('type' => 'file'));
echo $this->Form->file('file');
echo $this->Form->error('file');
echo $this->Form->submit(__('Upload'));
echo $this->Form->end();
In the quick start there is two upload methods: uploadImage and uploadDocument
but in the controller they use "upload".
There is another association in Products in the example, I need this?:
$this->hasMany('Documents', [
'className' => 'FileStorage.FileStorage',
'foreignKey' => 'foreign_key',
'conditions' => [
'Documents.model' => 'ProductDocument'
]
]);
I found this question (from there is the db I'm using) Getting Started with cakephp-file-storage quickstart guide and upload and insert but doesn't make the thumbnails and if I change the table to ImageStoreTable shows an error "Class not found"
So if anybody can help me I will be very grateful!
I got the problem to display the data from two table into JSON format and working on yii2 restful api.
this is my structure database:
TABLE `volunteer`(
`volunteer_id` int(11) NOT NULL auto_increment,
`state_id` int(11) null
TABLE `state`(
`state_id` int(11) NOT NULL auto_increment,
`state` varchar(225) null
volunteerController.php
public $modelClass = 'app\models\Volunteer';
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(),[
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['post'],
],
],
]);
}
config/web.php
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['volunteer','state','post']],
],
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => 'QMoK0GQoN7_VViTXxPdTISiOrITBI4Gy',
'parsers' => [
'application/json' => 'yii\web\JsonParser',
],
],
this is the result in JSON format:
[
{
"volunteer_id": 1,
"country_id": 1,
"state_id": 12,
}
]
so that result is not what I want. What I want is state_id should return state data from table state which means state : New York . Not return the state_id. How to solve this problem ?
This can be done with overriding fields() like that:
public function fields()
{
return [
'volunteer_id',
'country_id',
'state' => function ($model) {
return $model->state->name; // Return related model property, correct according to your structure
},
];
}
Additionally you can eagerly load this relation in prepareDataProvider() method using with().
Official docs:
Overriding fields()
Customizing actions
public function fields(){
return [
'volunteer_id',
'country_id',
'state' => function ($model) {
return $model->setOtherAttr($model->state_id);
},
'other_attr1',
'other_attr2',
];
}
public function setOtherAttr($state_id){
$state = State::find()->where(['state_id'=>$state_id])->one();
$this->other_attr1 = $state->other_attr1;
$this->other_attr2 = $state->other_attr2;
return $state->state;
}
Try the following code:
public function setOtherAttr($state_id){
if (($model = State::find()->where(['state_id'=>$state_id])->all()) !== null) {
return $model;
} else {
return '';
}
}
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.