I'm working on a small app to help learn cakephp 2.0. Its a simple task manager app.
Tables
users
tasks
tasktypes
I've a set up a foreign key in the tasks table called 'user_id'
As a task belongs to a user.
Now all I'm trying to do is display the tasks of a certain user and it wont work at all despite the sql query getting correct results when I tested it any help would be great.
In my taskscontroller I've the method;
//show the tasks of the current user
public function mytasks(){
$this->set('title_for_layout', 'Gerrys TaskManager: Display Tasks');
$this->Task->recursive = 0;
$tasks=$this->Task->find('all', array('conditions'=>array('username'=>'admin')));
$this->set('tasks', $this->paginate());
}
I'm trying to find the tasks of the user 'admin' or the "realted tasks" what am I doing wrong?
I suspect the problem maybe to with my models;
usermodel
/**
* hasMany associations
*
* #var array
*/
public $hasMany = array(
'Task' => array(
'className' => 'Task',
'foreignKey' => 'user_id'
)
);
task model;
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Tasktype' => array(
'className' => 'Tasktype',
'foreignKey' => 'tasktype_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
To get the tasks that belong to a user, it's much simpler to do that in the UsersController. Of course you have to set User hasMany Task in your UserModel.
Now you can create a function, for example to view the User and it's Tasks by id:
public function view($id = null) {
$this->User->id = $id;
$this->set('user', $this->User->read(null, $id));
}
The Tasks should now be available in your view by $user['tasks']. You have to pass the id in the url like: /path_to_your_app/users/view/1
You can always use debug() to see contents of an array or variable. To see what you've got add <?php debug($user);?> to your view.
You most likely want to list any tasks with a foreach in your view, very simple example::
foreach($user['Task'] as $task) {
echo $task['field1'] . ": " . $task['fied2'];
echo "<br />";
}
of course you might want to display the tasks in tables or something.
A good starting point for learning cakePHP is the Blog-Tutorial:
http://book.cakephp.org/2.0/en/getting-started.html#blog-tutorial
Related
I have a User model with two relations:
HasAndBelongsToMany
public $hasAndBelongsToMany = array(
'group' => array(
'className' => 'group',
'foreignKey' => 'user_id'
)
);
HasMany
public $hasMany = array(
'phonenumber' => array(
'className' => 'phonenumber',
'foreignKey' => 'user_id'
)
);
Phonenumber and Group have set
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
When I use
$this->saveAll(array(
'User' => $data,
'phonenumber' => $numbers, //array with data
'group' => $groups //array with data
)
);
The data gets saved in the tabels but User_id is "0" in phonenumber and group table.
How can I get the correct ID saved ? (CakePHP v 2.5)
FWIW saveAll() should work as advertised, populating the new user_id in the child tables in one fell swoop.
Have you paid attention to the relevant options: atomic & deep?
Especially if database does not support transactions, you'll need to pass in atomic:
$this->saveAll(array(
'User' => $data,
'phonenumber' => $numbers, //array with data
'group' => $groups //array with data
),
array('atomic' => false)
);
Considering the CakePHP documentation you will find this hint:
When working with associated models, it is important to realize that
saving model data should always be done by the corresponding CakePHP
model. If you are saving a new Post and its associated Comments, then
you would use both Post and Comment models during the save operation
(http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-hasone-hasmany-belongsto)
Based on that information I suggest you try the following:
$this->User->save($data); // first save the user
Assuming you have multiple numbers:
foreach($numbers as $key => $nbr) {
// set user_id related data
$numbers[ $key ]['Phonenumber']['user_id'] = $this->User->id;
}
Finally save your related data:
$this->User->Phonenumber->saveAll($numbers);
Since this code is untested you may need to take some adjustments. Always ensure to follow the Cake-Conventions and use CamelCase ModelNames.
For CakePHP 2
I would like to create a categories menu which would list the categories of my products. It's a 3 levels menu. Each category in the menu is a link that opens a page listing all the products that belong to it. So if the category is a parent one, it should list all the products contained in the children, the 2 sub-levels. Also, if the category is a children, it should link to a listing page of the products that belong to it.
With that said, here's what I've done so far.
I created my categories table according to cake's conventions with the following columns:
id--parent_id--lft--rght--name
Then my products' table:
id--name--slug--category_id
Now the Category.php model:
<?php
class Category extends AppModel {
public $name = 'Category';
public $actsAs = array('Tree');
public $belongsTo = array(
'ParentCategory' => array(
'className' => 'Category',
'foreignKey' => 'parent_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
public $hasMany = array(
'ChildCategory' => array(
'className' => 'Category',
'foreignKey' => 'parent_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
I'm using the ProductsController to render the categories menu because this is the page that will hold this categories menu:
<?php
class ProductsController extends AppController{
public $uses = array('Product');
public function index(){
$this->layout = 'products';
$this->loadModel('Category');
$this->set('data',$this->Category->generateTreeList());
}
}
and my index.ctp view:
<?php debug($categories); ?>
What i would like now is to build a nested ul-li menu of my categories that link to the products page they belong according to the tree.
<ul class="ulclass">
<li class="liclass">category</li>
</ul>
I checked only for this kind of tutorial, unfortunately I didn't find anything well explained, I did find a TreeHelper but i have no idea how to use it >>> TreeHelper from Github
However, I would like to have the control on my category's tree menu by having the possibility to add css classes. If you think this helper can provide me this construction so it's fine then. But I have no idea how to use it though. And not to mention that I'm new to CakePHP :( but I want to learn it because it's a great tool.
I forgot something about my DB, do I have to add any other column in my tables to make this system work or is it correct as is?
Last thing, as I didn't find anything for CakePHP 2 about this categories/products dynamic tree menu, I will share the entire code on Github so that it can help many others.
All right.
Assuming you use my updated version:
// in your controller
$categories = $this->Model->children($id);
// or
$categories = $this->Model->find('threaded', array(...));
Then pass it down to the view.
// in your view ctp
$categories = Hash::nest($categories); // optional, if you used find(all) instead of find(threaded) or children()
$treeOptions = array('id' => 'main-navigation', 'model' => 'Category', 'treePath' => $treePath, 'hideUnrelated' => true, 'element' => 'node', 'autoPath' => array($currentCategory['Category']['lft'], $currentCategory['Category']['rght']));
echo $this->Tree->generate($categories, $treeOptions);
And here an example of the element in /Elements/node.ctp:
$category = $data['Category'];
if (!$category['active'] || !empty($category['hide'])) { // You can do anything here depending on the record content
return;
}
echo $this->Html->link($category['name'], array('action' => 'find', 'category_id' => $category['id']));
Here is a simple solution, Used in controller for index view. Later you use it by two for each loops for each $posts as $post and foreach $post['Post']['children'].
$posts = $this->Post->find('all', array('conditions' => array('parent_id'=>null)));
foreach ($posts as $postKey => $post) {
$posts[$postKey]['Post']['children'] = $this->Post->find('all', array('conditions' => array('parent_id'=>$post['Post']['id'])));
}
$this->set('posts', $posts);
I have question in cakephp model,
I want to add dynamic condition in var $hasMany keyword
I want to add condition like current user_id, i got user Id after my login.
var $hasMany = array(
"AskComment"=>array('limit'=>3),
'AskStatistic',
'AskContactsLink',
'AskStatistic',
'AskObject',
'AskLikes'
);
If you want to add dynamic condition in your model, then you might have to bind the model association-ship dynamically into your controller's code. Write the following code into your controller's method for which you want to impose some new condition on the existing/new associated models.
$this->PrimaryModel->bindModel(array('hasMany' => array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array('AskComment.user_id' => $user_id)
)
)
));
Take a look at this link: Creating and destroying associations on the fly. This will surely help you to achieve the same.
I think its better to put your association in the construct function of your Model.
like this:
/**
* #see Model::__construct
*/
public function __construct($id = false, $table = null, $ds = null) {
public $hasMany = array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array(
'AskComment.user_id' => $user_id,
),
),
);
}
I am trying to do a search, using pagination for posts which have a specific tag or tags (for example, if a user was to select two tags, then posts containing either tag would be returned).
I have the relationship defined in my Posts table
public $hasAndBelongsToMany = array('Tags' => array(
'className' => 'Tags',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting'));
How do I use Find to retrieve rows with a given tag (name or ID would be fine)
Trying:
// other pagination settings goes here
$this->paginate['conditions']['Tags.id'] = 13;
gives me an error that the relationship does not exist.
Looking at the debug info it appears that the tables are not joining the Posts_Tags and Tags table, however, when I debug the data making it to the view, the Posts objects contain the tags data.
Most of the documentation I can find for this seems to revolve around earlier versions of CakePHP, any help would be appreciated.
Could not find a satisfying solution myself.
I created a behavior to take care of this.
Create a file called HabtmBehavior.php and put it in your app/Model/Behavior folder.
Put the block of code in there and save file.
Add the behavior to your model: eg public $actsAs = array('Habtm');
Here is a usage example with find.
<?php $this->Entry->find('all', array('habtm'=>array('Tag'=>array('Tag.title'=>'value to find'))) ?>
Paginate would look something like this:
$this->paginate['Entry']['habtm']['Tag'] = array('Tag.title'=>'value to find');
You are free to add as many relations as you want by adding additional Model Names in the habtm array.
(Just be careful not to make it to complex since this could start slowing down your find results.)
<?php
class HabtmBehavior extends ModelBehavior {
public function beforeFind(Model $model, $options) {
if (!isset($options['joins'])) {
$options['joins'] = array();
}
if (!isset($options['habtm'])) {
return $options;
}
$habtm = $options['habtm'];
unset($options['habtm']);
foreach($habtm as $m => $scope){
$assoc = $model->hasAndBelongsToMany[$m];
$bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$model->alias}.{$model->primaryKey}";
$options['joins'][] = array(
'table' => $assoc['joinTable'],
'alias' => $assoc['with'],
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind)
);
$bind = $m.'.'.$model->{$m}->primaryKey.' = ';
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
$options['joins'][] = array(
'table' => $model->{$m}->table,
'alias' => $m,
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind) + (array)$scope,
);
}
return $options;
}
}
Hope this helps.
Happy baking.
I think the best solution is apply find function on join table Model. I try this before and it's work fine.
in your PostTag model :
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
'Tags' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
);
in your controller :
// $tagsId = tags ids
$posts = $this->PostTag->find('all', array('conditions' => array('PostTag.tag_id' => $tagsId),'contain' => array('Post')));
also is better follow cake naming convention, if you have tags(plural), post_tags(first singular second plural),posts(plural) tables you must have Tag,PostTag,Post Models.
For some reason i'm really having a hard time wrapping my head around HABTM associations. I learn best by watching someone do something and explaining why. Anyways, I have 2 tables I want associated, Drugs, and SideEffects. I've created the intermediate table drugs_side_effects(has no data right now). Does cake put the data in that automatically or do I need to do something? The 3.7.6.5 hasAndBelongsToMany (HABTM) from the book didn't specify.
I've set up the models correctly(I think) and am not sure how to proceed at this point. It seems pretty simple. I need to display side_effect from the SideEffects table in a Drug view. I think in the edit_french controller function i'll need something like
$side_effect = $this->Drug->SideEffect->read(
array('SideEffect.id','SideEffect.side_effect'), $id);
$this->set('side_effect',$side_effect);
but I feel like that won't work as expected. Or maybe there's a more efficient way? Any advice or help is appreciated.
Drug Model:
var $hasAndBelongsToMany = array(
'SideEffect' => array(
'className' => 'SideEffect',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'drug_id',
'associationForeignKey' => 'side_effect_id'
)
);
}
?>
SideEffect Model:
var $hasAndBelongsToMany = array(
'Drug' => array(
'className' => 'Drug',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'side_effect_id',
'associationForeignKey' => 'drug_id'
)
);
}
?>
Drugs Controller:
<?php
class DrugsController extends AppController {
var $name = 'Drugs';
var $helpers = array('Html','Form','Paginator');
var $paginate = array(
//'contain' => array('SideEffect'),
//'fields' => array('Drug.id', 'Drug.generic'),
'fields' => array('Drug.id', 'Drug.generic','Drug.date_altered'),
'limit' => 50,
'order' => array(
'Drug.generic' => 'asc'
)
);
function index() {
$data = $this->paginate('Drug');
$alldrugs = $this->set('drugs', $this->Drug->find('all'));
$this->set('drugs', $data);
$this->set('alldrugs', $data);
//$this->set('lessdrugs', $this->paginate());
$this->set('title_for_layout','List of all current drugs');
}
function edit_french($id = null) {
$this->Drug->id = $id;
$drug = $this->Drug->read(
array(
'Drug.id','Drug.generic','Drug.ahl','Drug.aap','Drug.rid','Drug.oral','Drug.mw','Drug.clinical_recommendations'
),
$id
);
$this->set('title_for_layout', 'Translate clinical recommendations - ' . $drug['Drug']['generic']);
$this->set('drug',$drug);
if (empty($this->data)) {
$this->data = $this->Drug->read();
} else {
if ($this->Drug->save($this->data)) {
$this->Session->setFlash('The drug has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
You can pull the related data using ContainableBehavior. To do so, simply run a find on the Drug model and tell it to contain the associated SideEffect data.
$drug = $this->Drug->find('first', array(
'conditions' => array(
'Drug.id' => $id
),
'contain' => array(
'SideEffect'
)
));
You can also set the contain before using read() if you prefer.
$this->Drug->contain(array('SideEffect'));
$drug = $this->Drug->read(null, $id);
Using Containable allows you to gather all associated data in a single find() request.