Cortex hasMany not creating entries in m2m table - php

Hi I coudln't get Cortex hasMany to hasMany relation working so I made a simple test. I created two classes, CortexTestA and CortexTestB in my models namespace
namespace models;
use DB\Cortex;
class CortexTestA extends Cortex {
protected $fieldConf = array(
'name' => array(
'type' => 'VARCHAR256',
'nullable' => false
),
'cortextestb' => array(
'has-many' => array('models\CortexTestB', 'cortextesta', 'cortextest_a_b')
)
);
// constructor etc. follows
This is the field conf for CortexTestB:
'cortextesta' => array(
'has-many' => array('models\CortexTestA', 'cortextestb', 'cortextest_a_b')
)
Next I ran the setup command
\Models\CortexTestA::setup();
\Models\CortexTestB::setup();
But already something strange happens, both tables now have obsolete fields:
CREATE TABLE IF NOT EXISTS `cortextesta` (
`id` int(11) NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`cortextestb` int(11) DEFAULT NULL
)
CREATE TABLE IF NOT EXISTS `cortextestb` (
`id` int(11) NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`cortextesta` int(11) DEFAULT NULL
)
The m2m table does get created correctly though:
CREATE TABLE IF NOT EXISTS `cortextest_a_b` (
`id` int(11) NOT NULL,
`cortextesta` int(11) DEFAULT NULL,
`cortextestb` int(11) DEFAULT NULL
)
But now when I run this
$cta = new \models\CortexTestA();
$cta->name = "SomethingA";
$cta->save();
// Results in: INSERT INTO `cortextesta` (`id`, `name`, `cortextestb`) VALUES
// (1, 'SomthingA', NULL);
and then this:
$cta = new \models\CortexTestA();
$cta->load(array('id = ?', 1));
$ctb = new \models\CortexTestB();
$ctb->name = "SomethingB";
$ctb->cortextesta[] = $cta;
$ctb->save();
the relationship table cortextest_a_b remains empty.
What am I doing wrong?

When the relation is still empty, the property getter currently returns NULL. That's why the array modification unfortunately doesn't work. You can easily workaround that like this:
if (!$ctb->cortextesta)
$ctb->cortextesta = array($cta);
else
$ctb->cortextesta[] = $cta;
$ctb->save();
I'll see if I can optimized this a bit. The issue about the obsolete fields is indeed a bug. I'll patch that soon. thanks.

Related

Assigning the value of a table row ID with another ID using an SQL statement

So in my database i have two tables. Jokes and Comments. I want the ability to assign the post_id of the comment, to the joke_id of the joke, so it will assign and retrieve the comments relating to that joke. My problem is that i suck at writing SQL statements and haven't the foggiest on how to join two tables to make this happen.
My jokes table looks like this:
CREATE TABLE IF NOT EXISTS `jokes` (
`joke_id` int(11) NOT NULL AUTO_INCREMENT,
`joke` varchar(1024) NOT NULL,
`category_id` int(11) NOT NULL,
`vote` int(255) NOT NULL,
`date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`joke_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
and my comments table looks like this:
CREATE TABLE IF NOT EXISTS `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(40) NOT NULL,
`comment` text NOT NULL,
`joke_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
and for the moment, I am grabbing the data by assigned the $post_id = "1", but i want to change it to something like $post_id = $joke_id (with the joke id being in the same function, but i have no idea how to do it).
I'm using a MVC with codeigniter if thats any help.
Inside my controller, i have a php file called comments which has a function called insertComment, which looks like this:
public function insertComment(){
//extracts the data from the ajax
extract($_POST);
if($_POST['act'] == 'add-com'){
//assigned the db rows with the actual data which was inputted
$data = array(
'name' => htmlentities($name),
'comment' => htmlentities($comment),
//id_post should correlate to the joke_id
'id_post' => $id_post = "1"
);
$this->comments_m->insertComment($data);
}
and my insertComment function, inside the models of comment_m function looks like this:
function insertComment (){
extract($_POST);
if($_POST['act'] == 'add-com'){
$data = array(
'name' => htmlentities($name),
'comment' => htmlentities($comment),
'id_post' => $id_post = "1"
);
if(strlen($data['name']) <= '1'){
$data['name'] = 'Guest';
}
$this->db->insert('comments', $data);
}
}
To finalise, it would be a great help if someone could help with an SQL statement which joins the two tables together, which the joke_id having the same value as the comment's post_id which will make it unique to that joke.
Thank you
The SQL to join these two tables is -
SELECT `jokes`.*, `comments`.*
FROM `jokes`
LEFT OUTER JOIN `comments`
ON `jokes`.`joke_id` = `comments`.`joke_id`
This will return all of the comments for each joke. You can then filter or limit by adding the WHERE clause(s) -
WHERE `jokes`.`joke_id` = 1

How To Build Relations To Display Columns Of 4 Tables In Cgridview

I have a requirement where I need to build the relations between more than 3 tables.
I have 4 tables namely, Message, Flat, Person, Mapping tables.
Now, below tables have the following fields:
Message:
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Mapid` int(11) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `FK41715B218022FC0` (`MapId`)
Mapping
`Id` int(11) NOT NULL AUTO_INCREMENT,
`FlatId` int(11) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `FKE2B3C68A24F94F50` (`FlatId`),
Flat
`Id` int(11) NOT NULL AUTO_INCREMENT,
`PersonId` int(11) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `FK2FFF79122B94A6` (`PersonId`),
Person
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(255) DEFAULT NULL,
`FlatId` int(11) DEFAULT NULL,
`Phone` varchar(255) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `FKC4E39B55AF5432C` (`FlatId`),
Now, I have to build relations in such a way that in the Cgridview(admin.php) of Message, i should display PersonId of flat table and Name and Phone of Person table along with the columns of Message table.
I have defined relations like this in model class of message(message.php)
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'mapping' => array(self::BELONGS_TO, 'Mapping', 'MapId'),
'flat'=>array(self::HAS_ONE,'Flat',array('FlatId'=>'Id'),'through'=>'mapping'),
'person'=>array(self::HAS_ONE,'Person',array('PersonId'=>'Id'),'through'=>'flat'),
);
}
Can anyone explain me the step by step procedure to display the columns of person table in message gridview.
With assuming you can create dataProvider for the CGridView:
<?php
$this->widget('zii.widgets.grid.CGridView',array(
'id'=>'message-grid',
'dataProvider'=>$yourDataProvider //such as $model->search();
'filter'=>$model,
'columns'=>array(
'Id',
'Mapid',
'person.name',
'person.FlatId',
'person.Phone',
));
?>
You can pass a DataProvider to the view and use it in CgridView widget or use a 'search()' action from $model.
You can personalize the relation columns like this:
<?php
$this->widget('zii.widgets.grid.CGridView',array(
'id'=>'messagePerson-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'Id',
'Mapid',
array(
'header'=>'Person Name', // Personalize column name
'value'=>'$data->flat->person->Name',
'htmlOptions'=>array('style'=>'width:10%;'), // Personalize html attributes
),
'flat.person.FlatId', // Or directly with default relation name.
'flat.person.Phone',
));
?>

How do I configure my Yii model for relational queries?

A bit new to yii and have been having trouble trying to do a join query in my gii-generated model.
Summary:
I want to return videos (table 'videos') that have met specific search criteria. To do this, I have my 'videos' table, and I have another table 'searchmaps'. All searchmaps does is associate a video_id to a search_id so that I can keep track of multiple videos that met criteria for a single search scenario..
What I've tried:
I tried following yii docs for relational queries but I guess I'm missing something still... Below is my code. What am I doing wrong??
(Note: I wish to return a model using CActiveDataProvider)
Tables:
CREATE TABLE IF NOT EXISTS `videos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`directory` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`description` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`category` int(2) NOT NULL,
`tags` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`filetype` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`duration` int(11) NOT NULL,
`status` int(1) NOT NULL,
`error` text COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=17 ;
CREATE TABLE IF NOT EXISTS `searchmaps` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`search_id` int(11) NOT NULL,
`video_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=69 ;
Classes:
Here is the Controller class:
//From VideosController.php
...
public function actionIndex($searchmap_id)
{
$dataProvider = new CActiveDataProvider('SearchVideos', array(
'criteria' => array(
'with' => array('search.video_id','search.search_id'),
'together' => true,
'condition'=>'videos.id = search.video_id AND search.search_id='.$searchmap_id,
)));
$this->render('index',array(
'dataProvider'=>$dataProvider,
));
}
Below is the main model class:
// From Videos.php
...
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'search'=>array(self::BELONGS_TO, 'Searchmaps', 'video_id'),
);
}
Here is the model class of the related table
// From Searchmaps.php
...
/**
* #return array relational rules.
*/
public function relations()
{
// Each row has a search_id and a video_id relating to a specific video
// Multiple rows may have different videos but share the same search_id
return array(
'video'=>array(self::HAS_ONE, 'Videos', 'video_id'),
);
}
First, I would suggest using InnoDB tables so you can set up proper foreign keys -- if you do this then gii will generate the basic relations for you. If you can convert your tables, then you can add the fk with:
ALTER TABLE `searchmaps`
ADD CONSTRAINT `searchmaps_ibfk_1` FOREIGN KEY (`video_id`) REFERENCES `videos` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
Your relations don't look quite right, seems like they should be:
in Videos model:
return array(
'searchmaps' => array(self::HAS_MANY, 'Searchmaps', 'video_id'),
);
in Searchmaps model:
return array(
'video' => array(self::BELONGS_TO, 'Videos', 'video_id'),
);
then your dataProvider can look something like:
$dataProvider=new CActiveDataProvider('Videos',array(
'criteria'=>array(
'with'=>'searchmaps',
'together' => true,
'condition' => 'searchmaps.search_id='.$search_id,
)
));
to try it you can output a simple grid in your view with something like:
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'videos-grid',
'dataProvider'=>$dataProvider
));
Again, I would highly recommend using foreign keys in your table and view the relations gii outputs and once you understand what it's doing, it will be much easier to customize. Also, using foreign keys will insure the relationships are maintained. You can use a tool like MysqlWorkbench or similar if you need help creating the foreign keys.

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

hasMany combo box in cakePHP

I am a new cake baker. I developing a web site for cars, where each carcategory has many cars, and each car belongs only to one carcategory. In my Cars model, I wrote the following code:
class Cars extends AppModel
{
var $name = 'Cars';
var $belongsTo = array('Carcategory','User');
}
And in my Carcategory model, I wrote the following code:
class Carcategory extends AppModel {
var $name = 'Carcategory';
var $hasMany = array(
'Car' => array(
'className' => 'Car',
'foreignKey' => 'carcategory_id',
'order' => 'carcategory.name ASC',
'dependent'=> true
)
);
}
And in my car_controller, I wrote the following code:
function beforeFilter(){
$this->set('car_categories',$this->Carcategory->find('list', array('order' => 'name')));
}
And in my car view, I wrote the following code:
echo $form->input('carcategory_id', array('options' => $car_categories, 'label' => 'Cars Categories : ', 'class' => 'short'));
My database tables are as the following:
CREATE TABLE `carcategories` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`image` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE `cars` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) character set utf8 collate utf8_bin NOT NULL,
`name` varchar(255) character set utf8 collate utf8_unicode_ci NOT NULL,
`model` varchar(100) character set utf8 collate utf8_unicode_ci NOT NULL,
`motorcc` varchar(100) character set utf8 collate utf8_unicode_ci NOT NULL,
`details` text character set utf8 collate utf8_unicode_ci NOT NULL,
`carcategory_id` int(11) NOT NULL,
`userid` int(11) NOT NULL,
`hits` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
I have named the first table carcategory, because I might add another services to my site, and these services might have other categories
My code generates the following error
Notice (8): Undefined property: CarsController::$Carcategory [APP\controllers\cars_controller.php, line 14]
Fatal error: Call to a member function find() on a non-object in c:\wamp\www\work\cake\app\controllers\cars_controller.php on line 14
I cannot figure out what is the wrong in my code. Please give me a hand
Thanks
First off, you're not sticking to the CakePHP naming conventions. (Check them out here) Doing this would help you spot little mistakes, like the classname for your model is Cars when it should be Car. I would also rename your table to car_categories and hence all occurrences of Carcategory to CarCategory.
models/car.php
class Car extends AppModel {
var $name = 'Car';
var $belongsTo = array('CarCategory', 'User');
}
models/car_category.php
class CarCategory extends AppModel {
var $name = 'CarCategory';
var $hasMany = array(
'Car' => array(
'className' => 'Car',
'foreignKey' => 'car_category_id',
'order' => 'CarCategory.name ASC',
'dependent' => true
)
);
}
controllers/cars_controller.php
function beforeFilter() {
$this->set('carCategories', $this->Car->CarCategory->find('list'));
}
Note: You don't need to specify the order because you've already mentioned it in the model as the default order. Also, note the naming conventions for the view variable. This would let us do some CakePHP magic and simply type this in the view:
view
echo $form->input('car_category_id', array('class' => 'short'));
Again, not to be pedantic, but the label should be Car Category instead of Car Categories as you have it now, since a Car can belong to only one category according to your data model.
If you want the ":" separator for all your labels, you can use CSS to add them. Check here to find out how. You can also specify the between option for the form input.
Tables
Rename the table carcategories to car_categories
Rename the column carcategory_id in the cars table to car_category_id
Coming back to your actual problem:
Undefined property: CarsController::$CarCategory
This means that you cannot access CarCategory model from the CarsController. By default, the CarsController would manage the Car model. In order to manage the CarCategory model, you can do it in one of many ways:
Using associations: $this->Car->CarCategory->find( ... )
In your controller, set the $uses variable as follows: var $uses = array('Car', 'Carcategory');
You can load models on-the-fly as well (using loadModel or ClassRegistry::init if you're interested), but since you're fetching the list of Car Categories in the beforeFilter, you should stick to one of the above methods (preferably the associations).
Hope that helps. Remember to look carefully at the names of all your models, controllers, files and tables.
In your car controller the find should be
$this->set('car_categories',$this->Car->Carcategory->find('list', array('order' => 'name')));

Categories