I have two tables, 'users' and 'posts', looking like this:
users:
- id
- username
- password
...
posts:
- id
- user_id (foreign key referencing users.id)
- text
Basically, a user has multiple posts (blog-type posts). Now, I'm trying to create a new post as a logged in user, but I can't get it to work. Here's what I've done:
// 'User' model
class User extends AppModel
{
public $name = 'User';
public $hasMany = array('Post');
...
// 'Post' model
class Post extends AppModel
{
public $name = 'Post';
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
// In PostsController
public function create()
{
if($this->request->is('post'))
{
$this->Post->create();
if($this->Post->save($this->request->data)
{
// Success
}
}
}
// In the post view
<?php echo $this->Session->flash('auth'); ?>
<?php echo $this->Form->create('Post', array('action' => 'create')); ?>
<fieldset>
<legend>
<?php echo __("Write a post"); ?>
</legend>
</fieldset>
<?php echo $this->Form->end(__('Post')); ?>
If I write a post and click 'Post', I get an integrity constraint violation:
Error: SQLSTATE[23000]: Integrity constraint violation:
1452 Cannot add or update a child row: a foreign key
constraint fails (`yams`.`posts`, CONSTRAINT `user_id`
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
ON DELETE NO ACTION ON UPDATE NO ACTION)
Am I missing something here? It looks like the user id is not saved to the model.
EDIT:
I forgot to mention, the database error also prints out the SQL query which is clearly wrong:
INSERT INTO `yams`.`posts` (`text`) VALUES ('this is a test post.')
There's no ID whatsoever...
You need to do this:
// In PostsController
public function create()
{
if($this->request->is('post'))
{
$this->request->data['Post']['user_id'] = $this->Auth->user('id');
$this->Post->create();
if($this->Post->save($this->request->data)
{
// Success
}
}
}
I am just copying the book here, i have not used cakePHP at all!
According to the book: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html then a 'hasMany' relationship should look similar to:
class User extends AppModel {
public $hasMany = array(
'Recipe' => array(
'className' => 'Recipe',
'conditions' => array('Recipe.approved' => '1'),
'order' => 'Recipe.created DESC'
)
);
}
You have:
public $hasMany = array('Post');
Should there be mention of a classname in yours?
i.e.
public $hasMany = array(
'Post' => array(
'className' => 'Post'
)
);
With this then the ORM can work out how the classes relate and what to fill in at run time.
Related
I work with Yii 1.1. I wish to make a create form, which creates a module. A module belongs to one user. A user has many modules.
tbl_user
----------
id
firstName
lastName
email
password
role
and
tbl_user_module
----------
id
name
academicYear
....
userId [FOREIGN KEY]
The relations in models are set up as follow:
Module.php
public function relations()
{
return array(
'user' => array(self::BELONGS_TO, 'User', 'userId'),
);
}
User.php
public function relations()
{
return array(
'module' => array(self::HAS_MANY, 'Module', 'userId'),
);
}
A user uses a from to create a module, which belongs to him (logged in user). In the controller, I need to assign the userId foreign key as the logged in user.
public function actionCreate()
{
$module = new Module();
// collect user input data
if(isset($_POST['Module']))
{
$module->attributes=$_POST['Module'];
$module->userId = Yii::app()->user->id; //assigned userID as logged in user
if($module->validate())
{
if($module->save())
{
$this->redirect(array('home/index'));
}
}
}
//render
$this->render('create',array('model'=>$module));
}
The form is not saved and throws an error:
Property "Module.$userId" is not defined.
Obviously, I am not doing it right. How do I properly save a one-to-many relations in a form?
Solved, I had a typo in Module.php!
I'm trying to join my comment table with my user table like this comment.userId=user.id
unfortunately when i print_r($this->user); i get nothing. what am i doing wrong here?
in my comment model
public function relations()
{
return array(
'user' => array(self::BELONGS_TO, $this->module->user, 'userId'),
);
}
public function getLastName()
{
print_r($this->user);
die;
return is_null($this->user) ? '' : $this->user->{$this->module->lastNameAttribute};
}
where
$this->module->user = 'User'; //User is the model name
and
$this->module->lastNameAttribute = 'last_name';
in my view
$comments = $model->getCommentDataProvider();
$comments->setPagination(false);
$this->widget('zii.widgets.CListView', array(
'dataProvider'=>$comments,
'itemView'=>'application.modules.comment.views.comment._view', //view file location
'emptyText' => '<div class="alert alert-info">No comments yet.</div>',
'summaryText' => '<h4>'.Yii::t('commentTitle','{n} comment|{n} comments',$comments->totalItemCount).'</h4>'
));
I see a small typo, maybe you mistaken while making a post:
comment.userid=user.id
here it's userid and in relation you referenced it with userId
check it out please
EDIT - after question edit
I'm not familiar with CommentableBehavior but it seems to me that you need to eager load User model with each Comment:
$comments = Yii::createComponent($this->module->commentModelClass)->with('user')->findAll($this->getCommentCriteria());
I added with('user') in getComments() method
I am a newbie in CakePHP 1.3... I want to display or show the USER's username instead of USER's id, who post the COMMENTS in the POST view... Anyone can help me please?
Here is the model association I made:
POSTS 'has many' COMMENTS
COMMENTS 'belongs to' USERS
POST->COMMENT->USER
I already read the Containable Behavior of CakePHP 1.3, but still I can't understand it well... Please help me what codes to put in the post_controller's view & view.ctp that can show the related's related table in the POST view.
And How to call the USER's data in the POST view.
I'm still confused.
Thanks in Advance, Azure
Assumptions
You have three tables as below
(1) Posts
* id
* title
(2) Comments
* id
* post_id
* user_id
(3) Users
* id
* name
View Function in PostsController.php file
public function view($id) {
if (!$id) {
throw new NotFoundException(__('Invalid post'));
}
$this->Post->recursive=2;
$post = $this->Post->findById($id);
if (!$post) {
throw new NotFoundException(__('Invalid post'));
}
$this->set('post', $post);
}
Content of view.ctp file in app/View/Posts/ folder
<!-- File: /app/View/Posts/view.ctp -->
<h1><?php echo 'Post ID : '.h($post['Post']['id']); ?></h1>
<h1><?php echo 'Post Title : '.h($post['Post']['title']); ?></h1>
<?php
echo 'Comments By Users : ';
if(!empty($post['Comment'])){
foreach ($post['Comment'] as $key=>$value){?>
<p>User Name : <?php echo $value['User']['name'];?></p>
<?php }
}
else {
echo '<br/>';
echo 'No Comments Yet';
} ?>
Model File : User.php
<?php
class User extends AppModel {
public $hasMany = array(
'Comment' => array(
'className' => 'Comment',
)
);
}
?>
Model File : Comment.php
<?php
class Comment extends AppModel {
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
}
?>
Model File : Post.php
<?php
class Post extends AppModel {
public $hasMany = array(
'Comment' => array(
'className' => 'Comment',
)
);
}
?>
I assume you are saving user id in comments table and user names are in users table who posts the comment, then use the below solution
In Controller method:
$userdata=$this->User->find('all',array('fields'=>array('id','username'),'recursive'=>-1));
$userid=Set::extract('/User/id', $userdata);
$username=Set::extract('/User/username', $userdata);
$data=array_combine($username,$userid);
$this->set('name',$data);
In View:
$cid=$var['Comment']['user_id'];
$username=array_search($cid, $name);
echo $username;
Info
I'm having some issues with saving a model that has many MANY_MANY relationships. I have a page where you can add product attributes as well as product attribute levels. What I now want to do is add support for this on the update-page on the product. So when I enter the update-page, I will see all product attributes, and for each product attribute, there will be a drop-down list with the related product attribute levels for that specific product attribute.
Database
Product
id
etc
ProductAttribute
id
etc
ProductAttributeLevel
id
product_attribute_id ## FK
etc
ProductProductAttributeLevel -- This is the pivot-table
product_id ## FK PK
product_attribute_level_id ## FK PK
ActiveRecords
Product:
class Product extends S360ActiveRecord {
public function behaviors() {
return array('CAdvancedArBehavior' => array(
'class' => 'application.extensions.CAdvancedArBehavior')
);
}
public function rules() {
return array(
array('attributeLevels', 'safe'),
);
}
public function relations() {
return array(
'attributeLevels' => array(self::MANY_MANY,
'ProductAttributeLevel',
'product_product_attribute_level(product_id,product_attribute_level_id)'
),
);
}
}
ProductAttribute:
class ProductAttribute extends S360ActiveRecord {
public function relations() {
return array(
'levels' => array(self::HAS_MANY, 'ProductAttributeLevel', 'product_attribute_id'),
);
}
}
ProductAttributeLevel:
class ProductAttributeLevel extends S360ActiveRecord {
public function relations() {
return array(
'productAttribute' => array(self::BELONGS_TO, 'ProductAttribute', 'product_attribute_id'),
'products' => array(self::MANY_MANY, 'Product', 'product_product_attribute_level(product_attribute_level_id,product_id)'),
);
}
}
ProductProductAttributeLevel:
class ProductProductAttributeLevel extends S360ActiveRecord {
public function relations()
{
return array(
'productAttributeLevel' => array(self::BELONGS_TO, 'ProductAttributeLevel', 'product_attribute_level_id'),
'product' => array(self::BELONGS_TO, 'Product', 'product_id'),
);
}
}
My ProductController method that updates a product looks like this:
public function actionUpdate($id) {
$model = $this->loadModel($id);
$this->performAjaxValidation($model);
if (isset($_POST['Product'])) {
$model->attributes = $_POST['Product'];
if ($model->save()) {
$this->redirect(array('index'));
}
}
$this->render('update', array('model' => $model));
}
Relevant part in my form-view:
<?php
$form=$this->beginWidget('S360ActiveForm', array(
'id' => 'product-form',
'enableAjaxValidation' => true,
));
?>
<?php $attributes = ProductAttribute::model()->findAllByAttributes(array('survey_id' => $model->parent_id)); if ($attributes): ?>
<div class="span6">
<?php foreach ($attributes as $attribute) {
echo $form->dropDownListRow($model, 'attributeLevels',
CMap::mergeArray(
array('0' => Yii::t('backend','No attribute level')),
CHtml::listData($attribute->levels, 'id', 'label')
),
array('class' => 'span5')
);
}?>
</div>
<?php endif; ?>
Issue
I get this CDBException:
CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (product_product_attribute_level, CONSTRAINT product_product_attribute_level_ibfk_2 FOREIGN KEY (product_attribute_level_id) REFERENCES product_attribute_level (id) ON DELE). The SQL statement executed was: insert into product_product_attribute_level (product_id, product_attribute_level_id) values ('5', '0')
Problem is though that product_attribute_level with id "0" does not exist, the id's starts at "1". How would I change it so that it inserts the correct id-number?
Example of what I want
Let's say I have 2 product attributes; Attribute1 and Attribute2.
Attribute1 have product attribute levels Attribute1_Level1 and Attribute1_Level2.
Attribute2 have product attribute levels Attribute2_Level1, Attribute2_Level2 and Attribute2_Level3.
When I go to my Product edit-/update -page, I want to see this:
Attributes http://img201.imageshack.us/img201/9252/screenshot20130207at103.png
Attribute2 Dropdown http://img405.imageshack.us/img405/9252/screenshot20130207at103.png
The Product belongs to a Survey. The Product Attribute's belongs to a Survey as well so fetching all the Product Attributes that the Product can have is easy:
$attributes = ProductAttribute::model()->findAllByAttributes(array('survey_id' => $product->survey_id));
After this I need to fetch all Product Attribute Levels that belongs to each attribute, which is quite easy as well:
foreach ($attributes as $attribute) {
echo $form->dropDownList($attribute, 'label',
CHtml::listData($attribute->levels, 'id', 'label'),
$htmlOptions
);
}
The problem is how to connect it with the Product and have its "$product->attributeLevels" relationship update accordingly based on what I select from the different dropdowns. $product->attributeLevels should be a list of ProductAttributeLevel and should be stored via the table "product_product_attribute_level".
And of course you are selecting from the dropdown? because if not you are indeed sending a '0'
<?php foreach ($attributes as $attribute) {
echo $form->dropDownListRow($model, 'attributeLevels',
CMap::mergeArray(
// **HERE**
array('0' => Yii::t('backend','No attribute level')),
CHtml::listData($attribute->levels, 'id', 'label')
),
array('class' => 'span5')
);
}?>
If what you want is to have something as the first option that doesn't represents a record, there are two options, use the prompt or the empty attributes of dropDownList, from the docs:
prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be
HTML-encoded.
empty: string, specifies the text corresponding to empty selection. Its value is empty. The 'empty' option can also be an array
of value-label pairs. Each pair will be used to render a list option
at the beginning. Note, the text label will NOT be HTML-encoded.
Now, you want a dropdown list of attributeLevels, but you want them saved on the product. so iterate over the attributes, get its levels, but save them on the product, like this:
<?php foreach ($attributes as $i => $attribute) {
echo $form->dropDownListRow($product, "[$i]attributeLevels",
CHtml::listData($attribute->levels, 'id', 'label'),
array('class' => 'span5', 'prompt' => 'No attribute level')
);
}?>
Now to save them on your product, do this in your controller:
public function actionUpdate($id) {
$model = $this->loadModel($id);
$this->performAjaxValidation($model);
if (isset($_POST['Product'])) {
$attrLevels = $_POST['Product']['attributeLevels'];
unset($_POST['Product']['attributeLevels']);
$model->attributes = $_POST['Product'];
if( $model->save() ) {
$valid=true;
foreach($attrLevels as $i=>$attrLevel)
{
$pivot = new ProductProductAttributeLevel;
$pivot->product_id = $model->id;
$pivot->product_attribute_level_id = $attrLevel;
$valid=$item->save() && $valid;
}
if($valid){
$this->redirect(array('index'));
}
}
}
$this->render('update', array('model' => $model));
}
Disclaimer: copy/paste may not work, but you get the idea
In my application I have two tables (I have more but I'm only discussing two here):
**Posts**
id
title
datetime
content
**Comments**
id
content
post_id
user_id
As you can see their is a relationship using the post_id as a foreign key.
When viewing the Post I have the following code to display comments:
<?php if ( ! empty($post['Comment']) ): ?>
<ul>
<?php foreach ($post['Comment'] as $comment): ?>
<li>
<?php echo $comment['title']; ?>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>No comments...</p>
<?php endif; ?>
Here is the controller method for viewing a post:
function view ( $id = null, $slug = null )
{
$post = $this->Post->find('first',array('contain'=>array('User'=>array('Comment','Profile')),'conditions'=>array('Post.id'=>Tiny::reverseTiny($id))));
if (!$post)
{
throw new NotFoundException('404');
}
else if($post['Post']['status'] == '0') // 0=draft 1=open 2=open
{
if($post['Post']['user_id'] == $this->Auth->user('id'))
{
$this->Session->setFlash('Your post has NOT been published yet');
}
else
{
throw new NotFoundException('404');
}
}
if (Inflector::slug($post['Post']['title']) != $slug || $slug = null)
{
$this->redirect(array('id'=>Tiny::toTiny($post['Post']['id']),'slug'=>Inflector::slug($post['Post']['title'])));
}
$this->set(compact('post'));
}
and this is the comment model:
class Comment extends AppModel
{
public $name = 'Comment';
public $belongsTo = array('User','Post');
public $actsAs = array('Containable');
}
and a typical url for a post would be: /posts/Sample_post-1ns
However I ALWAYS get the message that there are no comments... But I have looked in the database and their is... and I have double checked that all the associations are correct. So I can only presume that the code above is wrong!
Also is their a better way of calling the comments? As I want to add the ability to filter and paginate them too.
Cheers
Seems you need something like this:
Comment model:
public $belongsTo = array('User', 'Post');
Post model:
public $belongsTo = array('User');
public $hasMany = array('Comment');
User model:
public $hasMany = array('Post', 'Comment');
And contain statement of Post:
'contain' => array('Comment', 'User' => array('Comment', 'Profile'))
From your schema it looks like the comment model is related to the post model, not to the user, so:
$post = $this->Post->find('first',array('contain'=>array('User'=>array('Comment','Profile')),'conditions'=>array('Post.id'=>Tiny::reverseTiny($id))));
Should be:
$post = $this->Post->find('first',array('contain'=>array('Comment','User'=>array('Profile')),'conditions'=>array('Post.id'=>Tiny::reverseTiny($id))));
I.e. the Comment should be outside of the User contain array.
Providing that the correct hasMany and belongsTo relationships are set up on the Post/User and Comment model respectively, the first example would produce an array like this:
array(
'Post'=>array(...),
'User'=>array(
'id'=>1,
//Other user data
'Comment'=>array(
array(...), //Comment 1
array(...), //Comment 2
)
)
);
And the second example would produce:
array(
'Post'=>array(...),
'User'=>array('id'=>1 //Other user data)
'Comment'=>array(
array(...), //Comment 1
array(...), //Comment 2
)
);
Hopefully, that shows that the difference is in the position of the "Comment" key in the array - it is either under the User key, or at the top level depending on how you do the contain.