Kohana 3.2 validate composite primary key - php

I have following table
create table `groupusers`(
`id` int not null auto_increment,
`user` varchar(100) not null,
`group` varchar(100) not null,
UNIQUE KEY(`id`),
PRIMARY KEY(`user`, `group`)
)
My model looks like this,
class Model_Groupuser extends ORM{
protected $_table_name = 'groupusers';
public function rules(){
return array(
'user' => array(
array('not_empty'),
array(array($this, 'user_group_not_exists')),
),
'group' => array(
array('not_empty'),
array(array($this, 'user_group_not_exists')),
)
);
}
public function user_group_not_exists($param){
// Need to get other field's value here.
}
}
Problem is every time user_group_not_exists is called, its called with a single parameter. Either user or group. But I need both to determine if the combination exists in the db already.
How can I get current model's fields' value?

You can get other fields value using $this->object() function.
public function user_group_not_exists($user_or_group){
$obj = $this->object();
$group = $obj['group'];
$user = $obj['user'];
// Check if ($group, $user) pair exists in db here
}

You have not really named your table columns comfortable. Naming them user and group and the relations also user and group creates ambiguity between the two.
As kohana does this great thing where you can access table fields, relationships etc. as if it's an objects property. $i_am_lazy = $object-><field,relation,whatever>. Now you named your fields and relations such that it is not clear what you are trying to get.
The only way you can access these id's now is like the following (or the hard way through $this->object() as stated in the other answer, both don't feel good anyway):
$user = $this->user->id;
$group = $this->group->id;
Though, I recommend just renaming the table columns.
create table `groupusers`(
`id` int not null auto_increment,
`user_id` varchar(100) not null,
`group_id` varchar(100) not null,
UNIQUE KEY(`id`),
PRIMARY KEY(`user`, `group`)
)
That way you can simply use $this->user_id or $this->group_id.

Related

Yii2 Linking two models not working when saving user-submitted data from form

I have two models: one for "Experts" (based on a database table of their contact details), and one for "Expertise" (such as 'PHP', 'JavaScript', 'Java', ..., also in database table). Since an expert can have more than one expertise, and an expertise can be held by several experts, this is a many-to-many relationship that is defined in the model classes, see below. The relationship should be stored in a junction table.
// in models/RcccExperts.php
class RcccExperts extends \yii\db\ActiveRecord
{
...
public function getRcccExpertise()
{
return $this->hasMany(RcccExpertise::className(), ['id' => 'expertise_id'])
->viaTable('rccc_experts_expertise', ['expert_id' => 'id']);
}
}
// in models/RcccExpertise.php
class RcccExpertise extends \yii\db\ActiveRecord
{
...
public function getRcccExperts()
{
return $this->hasMany(RcccExperts::className(), ['id' => 'expert_id'])
->viaTable('rccc_experts_expertise', ['expertise_id' => 'id']);
}
}
The user can add a new entry to the experts database via a form that contains data from both models. The user can assign expertise to the expert via a multiple select form field (implemented with select2 tags), where s/he can select from existing expertise (already in the database) or add new expertise tags.
When I try to link the two models to populate the junction table (that contains the expert IDs and expertise IDs), I get the following error message:
PHP Fatal Error – yii\base\ErrorException
Call to a member function getIsNewRecord() on array
in /Users/Sites/Yii/vendor/yiisoft/yii2/db/BaseActiveRecord.php at line 1248
I'm aware that when linking two models using ->viaTable, both models mustn't be newly created. In my case, the Expert model has just been saved and has a primary key id; I'm retrieving the Expertise ids that were selected for the Expert from the database.
Whichever way I try to link the two models, the problem seems to be that the Expertise is not recognized as an existing model, but as an array().
What am I doing wrong?!
More details on the error below:
* This parameter is only meaningful for a relationship involving a junction table
* (i.e., a relation set with [[ActiveRelationTrait::via()]] or [[ActiveQuery::viaTable()]].)
* #throws InvalidCallException if the method is unable to link two models.
*/
public function link($name, $model, $extraColumns = [])
{
$relation = $this->getRelation($name);
if ($relation->via !== null) {
if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.');
}
if (is_array($relation->via)) {
/* #var $viaRelation ActiveQuery */
list($viaName, $viaRelation) = $relation->via;
$viaClass = $viaRelation->modelClass;
// unset $viaName so that it can be reloaded to reflect the change
unset($this->_related[$viaName]);
} else {
...
After the form to create a new expert has been submitted, this is the Controller code to execute:
// in controllers/ExpertsController.php
/**
* ExpertsController implements the CRUD actions for RcccExperts model.
*/
class ExpertsController extends Controller
{
...
public function actionCreate()
{
$model = new RcccExperts();
$expertise = new RcccExpertise();
if ($model->load(Yii::$app->request->post()) && $expertise->load(Yii::$app->request->post())) {
if ($model->validate() && $expertise->validate()) {
if ($model->save()) {
// Once the Expert's model data has been saved
// Go ahead and process the Expertise ids form the multiple select form field:
// First save the expertise (coming from a multiple select form field where the user can
// select existing expertise tags or add new ones) to the database
$array = $expertise->expertise;
$expertise_ids = array();
foreach ($array as $key => $value) {
$exp = new RcccExpertise();
// Check that the expertise does not exist yet
$check1 = $expertise->find()->where(['id' => $value])->one();
$check2 = $expertise->find()->where(['expertise' => $value])->one();
if ($check1 == null && $check2 == null) {
$exp->expertise = $value;
// Save new expertise
$exp->save();
$result = $expertise->find()->select('id')->where(['expertise' => $value])->one();
$expertise_ids[] = $result->id;
}
else $expertise_ids[] = $value;
}
$expertise->id = $expertise_ids;
// Put the new expertise IDs in a model instance
$expertises = RcccExpertise::find()->where(['id' => $expertise->id])->all();
// Link expert model with expertise model
// to populate the junction table
$model->link('rcccExpertise', $expertises);
}
return $this->redirect(['view', 'id' => $model->id]);
}
}
else {
return $this->render('create', [
'model' => $model,
'expertise' => $expertise,
'languages' => $languages,
'attachments' => $attachments
]);
}
}
The MySQL tables holding this information look like this:
`CREATE TABLE `rccc_experts` (
`id` int(10) UNSIGNED NOT NULL,
`name` varchar(200) NOT NULL,
`email` varchar(100) NOT NULL,
`phone` varchar(100) NOT NULL,
`skype` varchar(100) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `rccc_experts`ADD PRIMARY KEY (`id`);
ALTER TABLE `rccc_experts` MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;COMMIT;
CREATE TABLE `rccc_expertise` (
`id` int(10) UNSIGNED NOT NULL,
`expertise` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `rccc_expertise` ADD PRIMARY KEY (`id`);
ALTER TABLE `rccc_expertise` MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;COMMIT;
CREATE TABLE `rccc_experts_expertise` (
`id` int(10) UNSIGNED NOT NULL,
`expert_id` int(10) UNSIGNED NOT NULL,
`expertise_id` int(10) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `rccc_experts_expertise` ADD PRIMARY KEY (`id`);
ALTER TABLE `rccc_experts_expertise` MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;COMMIT;
Relation hasMany returns array of objects. To make it work, you have to loop over this array, like:
foreach ($model->rcccExperts as $expert) {
// do your stuff here on $expert variable
}

Table name as Variable in Model Codeigniter

Is it possible that i can pass some variable to the model in table name?
I have a module that creates Customers.
Now for each Customers we create a separate table.
The Customers has a login and logout credentials.
Now for whenever a customer is created i can't always create a new model and pass it name there.
So i want that it gets dynamically, I am not sure how i can do it.
For each customer there will be one separate table.
Now i want to create a model for login, but what should i pass in the table name,it should be dynamic right?
What should be the mysql schema to get this done?
This my sample Model code:
class Customer_User_M extends MY_Model
{
protected $_table_name = 'customer1';
protected $_order_by = 'name';
public function __construct()
{
parent::__construct()
}
}
The Customer Table:
CREATE TABLE IF NOT EXISTS `customer1` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`site_key` varchar(50) NOT NULL,
`display_name` varchar(100) NOT NULL,
`ext` varchar(15) NOT NULL,
`auth_user` varchar(100) NOT NULL,
`password` varchar(128) NOT NULL,
`base_ini_id` varchar(50) NOT NULL,
`comments` varchar(200) NOT NULL,
`custom_ini_filename` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
You can simply make a function in the model and put the code of create table in the function and pass dynamic table name in the function from controller like below....
Model function code:
function access_customer_table($table_name)
{
//put ur select query here with table name in FROM clause will be $table_name
}
and call this model function in your controller after load the model... and put your table name as argument...
like :
$this->load->model('Customer_User_M');
$this->Customer_User_M->access_customer_table('new_customer_table_name');
Please correct syntax if needed.. let me know if anything wrong in the logic...
I tried to set the table name after loading the model. Just worked for me.
$this->load->model('customer_model','customer');
then,
$customer->table_name = "customer_xxx";
It is better to save common customer details in a customer table and create another tables to store different data of customers with different fields having foreign key customerid.
Here, if you want to pass dynamic tablename to model then you need to predefine all table names as an associative array with key-value pair.Then you can pass desired key-value to model.It can be implemented using a library.So that you can use it all your controllers.

Yii joining two table using relations in model

Hi I have these two tables that I want to join using relations in Yii, The problem is Im having a hard time figuring out how Yii relation works.
picturepost
id
title
link_stat_id
linkstat
id
link
post_count
I also have a working SQL query. This is the query I want my relation to result when I search when I want to get picturepost
SELECT picturepost.id, picturepost.title,linkstat.post_count
FROM picturepost
RIGHT JOIN linkstat
ON picturepost.link_stat_id=linkstat.link;
I want something like this when I search for a post.
$post = PicturePost::model() -> findByPk($id);
echo $post->linkCount;
Here's my table for extra info:
CREATE TABLE IF NOT EXISTS `picturepost` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`title` text COLLATE utf8_unicode_ci DEFAULT NULL,
`link_stat_id` char(64) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=MyISAM;
CREATE TABLE IF NOT EXISTS `linkstat` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`link` char(64) COLLATE utf8_unicode_ci NOT NULL,
`post_count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `post_count` (`post_count`),
KEY `link_stat_id` (`link`)
) ENGINE=InnoDB;
Thanks in advance I hope I explained it clearly.
There are a few tutorial regarding this, and I won't repeat them, but urge you to check them out.
The easiest starting point will be to create your foreign key constraints in the database, then use the Gii tool to generate the code for the model, in this case for the table picturepost.
This should result in a class Picturepost with a method relations(),
class Picturepost extends {
public function relations()
{
return array(
'picturepost_linkstats' => array(self::HAS_MANY,
'linkstat', 'link_stat_id'),
);
}
This links the 2 tables using the *link_stat_id* field as the foreign key (to the primary key of the linked table).
When you are querying the table picturepost, you can automatically pull in the linkstat records.
// Get the picturepost entry
$picturepost = PicturePost::model()->findByPk(1);
// picturepost_linkstats is the relationship name
$linkstats_records = $picturepost->picturepost_linkstats;
public function relations()
{
return array(
'linkstat' => array(self::HAS_ONE, 'Linkstat', array('link_stat_id'=>'link')),
);
}
More on yii relations.
This assumes that you have an active record model Linkstat that represents data in table linkstat.

Creating a lookup field in a CakePHP form

I have a view created using Bake that has the following:
<fieldset>
<legend><?php echo __('Edit Device'); ?></legend>
<?php
echo $this->Form->input('DeviceID');
echo $this->Form->input('DeviceTypeID');
echo $this->Form->input('UserID');
echo $this->Form->input('Type');
echo $this->Form->input('KeyPadID');
echo $this->Form->input('Version');
echo $this->Form->input('Description');
echo $this->Form->input('UpdateID');
?>
</fieldset>
Which saves to the table:
CREATE TABLE `device` (
`DeviceID` VARCHAR(255) NOT NULL ,
`DeviceTypeID` INT(11) NOT NULL ,
`UserID` INT(10) NOT NULL ,
`Type` VARCHAR(10) NULL DEFAULT NULL ,
`KeyPadID` INT(10) NULL DEFAULT NULL ,
`Version` VARCHAR(255) NULL DEFAULT NULL ,
`Description` TINYBLOB NULL ,
`UpdateID` INT(11) NULL DEFAULT NULL ,
PRIMARY KEY (`DeviceID`),
INDEX `FK_USER` (`UserID`),
INDEX `FK_devices_updates` (`UpdateID`),
INDEX `FK_device_devicetype` (`DeviceTypeID`),
CONSTRAINT `FK_device_devicetype` FOREIGN KEY (`DeviceTypeID`) REFERENCES `devicetype` (`DeviceTypeID`),
CONSTRAINT `FK_devices_updates` FOREIGN KEY (`UpdateID`) REFERENCES `update` (`ID`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `FK_USER` FOREIGN KEY (`UserID`) REFERENCES `user` (`UserID`) ON UPDATE CASCADE ON DELETE CASCADE
)
My problem is that when the form is displayed, it shows DeviceTypeID and UserID as well as UpdateID as the foreign key value instead of a drop down with the caption being the text and the value being the ID column. How would I go about setting a field from the foreign table to be the display field and the id as being the value?
Update 11-02-2013
First of all I strongly suggest to convert your primary and foreign keys accordingly
so that they meet the CakePHP naming conventions.
This means that:
DeviceID should be id.
DeviceTypeID should be device_type_id
UserID should be user_id
Also all primary keys in your tables should be named as id.
This way you will never have to worry about anything, concerning your models etc.
After that, all your tables must be in plural form. This means that device table should be devices, so you should rename it also.
I assume that you also have the following tables: devices_types and users.
At this point, I should notice that I would prefer to have a table named devicetype. I avoid underscored names, because it's very easy to make mistakes using the correct form for each object, class etc. So I don't have to worry whether I should use the CamelCase or not.
Anyway
Your Device model should be something like that:
<?php
/** Device.php **/
class Device extends AppModel {
public $name = 'Device';
public $belongsTo = array(
'DeviceType' => array(
'className' => 'DeviceType',
'foreignKey' => 'device_type_id'
/** Specify other keys that meet your needs **/
),
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
};
?>
Also your DeviceType model should be similar to
<?php
/** DeviceType.php **/
class DeviceType extends AppModel {
public $name = 'DeviceType';
};
In your edit() method, you should query your DeviceType in something like this:
...
$devicetypes = $this->Device->DeviceType->find('list', array('id', 'caption'));
$this->set(compact('devicetypes'));
...
This way in your view the respective form element sets the <select> menu correctly.
PS: You should follow the CakePHP conventions about model-naming etc... Mine was just an example.

Complex reference maps in Zend_Db_Table to account for multi-column keys

I am going to attempt to keep this as simple as possible, but the use case is outside the original intention of Zend_Db I fear. It concerns a set of tables I have for tagging pages (or anything else eg. documents) in a CMS.
I have three tables:
Pages (pages)
Tags (tags)
TagLink (tags_link) which is a many-to-many linking table between Pages and Tags
Pages is a simple table (I have removed the inconsequential columns from the code below):
CREATE TABLE `pages` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
FULLTEXT KEY `search` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
Tags is quite simple as well although there is a self-referential column (parent_tag_id):
CREATE TABLE `tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag` varchar(255) NOT NULL,
`parent_tag_id` int(11) NOT NULL DEFAULT '0',
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `GetByParentTagId` (`parent_tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
TagLink is again fairly simple:
CREATE TABLE `tags_link` (
`tag_id` int(11) NOT NULL,
`module_type` varchar(50) NOT NULL,
`foreign_key` int(11) NOT NULL,
UNIQUE KEY `Unique` (`tag_id`,`module_type`,`foreign_key`),
KEY `Search` (`module_type`,`foreign_key`),
KEY `AllByTagId` (`tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
The complicating factor is that TagLink is able to link against any other table in the database and not just Pages. So if for example I had a documents upload section then that could also be tagged. To facilitate this way of working there is effectively a multi-column key.
To make this clearer I will demonstrate a couple of insert queries that might be run when tags are added to a table (eg. Pages):
INSERT INTO `tags_link`
SET `tag_id` = '1',
`module_type` = 'Pages',
`foreign_key` = '2'
INSERT INTO `tags_link`
SET `tag_id` = '1',
`module_type` = 'Documents',
`foreign_key` = '3'
So as you can see the module_type column is simply an arbitrary string that describes where the foreign key can be found. This is not the name of the table however as anything with an ID can have tags linked to it even if it is not necessarily in the MySQL database.
Now to the Zend_Db_Table $_referenceMap in PageTable:
protected $_referenceMap = array(
'TagLink' => array(
'columns' => 'id',
'refTableClass' => 'Models_Tag_TagLinkTable',
'refColumns' => 'foreign_key'
),
);
But this does not take into account my arbitrary module_type column and will return any TagLink with the same foreign key. Obviously this is bad because you get TagLinks for documents mixed in with those for pages for instance.
So my question is how can I take into account this additional column when setting up this reference? The aim is to avoid having a TagLink class for each module_type as I have now.
I would imagine something like the following could explain my requirements although obviously this is not how it would be done:
protected $_referenceMap = array(
'TagLink' => array(
'columns' => 'id',
'refTableClass' => 'Models_Tag_TagLinkTable',
'refColumns' => 'foreign_key',
'where' => 'module_type = "Pages"'
),
);
My current implementation overrides the _fetch method in the Documents_TagLinkTable in the following way:
protected function _fetch(Zend_Db_Table_Select $select) {
$select->where("module_type = 'Documents_Secondary_Tags' OR module_type = 'Documents_Primary_Tags' OR module_type = 'Documents'");
return parent::_fetch($select);
}
As you can see there maybe more than one set of tags added to any object as well.
Example 3 in "Fetching Dependent Rowsets" in the Zend Framework reference demonstrates a technique you could use:
http://framework.zend.com/manual/en/zend.db.table.relationships.html
Whilst it doesnt show a "where" clause being included in the select, it should work.
Duncan

Categories