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')));
Related
I have a roles table. Looks like this:
CREATE TABLE `roles` (
`role` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`permissions` longtext COLLATE utf8_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `roles`
ADD PRIMARY KEY (`role`),
ADD UNIQUE KEY `role` (`role`);
Now cake is not recognizing it as a "normal" field, so it doesn't give out any input field.
I fixed my view with this:
// src/Template/Admin/Roles/add.ctp
echo $this->Form->control('name', ['class' => 'form-control']);
And now the workaround in my controller:
// src/Controller/Admin/RolesController.ctp
$roleData = $this->request->getData();
$roleData['role'] = strtolower($roleData['name']);
unset($roleData['name']);
$role = $this->Roles->patchEntity($role, $roleData);
if ($this->Roles->save($role)) {
$this->Flash->success(__('The role has been saved.'));
}
It saves the entry, but doesn't fill up anything in the database row role. Am I missing something?
If you are using patchEntity then you cannot assign non assignable fields and your primary key is more than likely not an assignable key by default. You can change it in the entity which should allow the form to show it will allow patch entity to work correctly.
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Role extends Entity
{
protected $_accessible = [
'role' => true,
'permissions' => true,
'*' => false,
];
}
https://book.cakephp.org/3.0/en/orm/saving-data.html#changing-accessible-fields
https://book.cakephp.org/3.0/en/orm/entities.html#mass-assignment
In RolesTable.php there should be something like this:
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('roles');
$this->setPrimaryKey('role');
}
so CakePHP would use 'role' as PrimaryKey.
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.
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.
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.
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