Changing the order items are retrieved from the DB in CakePHP? - php

I've made a forum using CakePHP while following a tutorial and would like to expand it.
Currently, in a post it allows me to comment, but they are displayed as newest -> oldest, so it doesn't make as much sense and I would like to reverse this.
This is the current association I have, I'm pretty new to PHP/Cake so I'm not sure where to go from here, any help is very appreciated!
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'forum_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
For those asking this is the function in my controller:
public function add($topicId=null) {
if ($this->request->is('post')) {
$this->request->data['Post']['user_id'] = $this->Auth->user('id');
if ($this->Post->save($this->request->data)) {
$this->Session->setFlash(__('Post has been created'));
$this->redirect(array('controller'=>'topics','action'=>'view',$this->request->data['Post']['topic_id']));
}
} else {
if (!$this->Post->Topic->exists($topicId)) {
throw new NotFoundException(__('Invalid topic'));
}
$this->Post->Topic->recursive = -1;
$topic = $this->Post->Topic->read(null,$topicId);
$this->Post->Forum->recursive = -1;
$forum = $this->Post->Forum->read(null,$topic['Topic']['forum_id']);
$this->set('topic',$topic);
$this->set('forum',$forum);
}
}

Something like this should do it:
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'forum_id',
'dependent' => false,
'order' => 'Post.created ASC' //order the Posts in ascending order based on whe they were created
)
Assuming you have a created column in your database table.
Edit:
You can find more info in the CakePHP documentation http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html

Avoid using read(), it is better to use find('first'). So you could rewrite your queries like:-
$this->Post->Topic->find('first', array(
'contain' => array(
'Post' => array(
'order' => array(
'Post.created' => 'asc',
'Post.id' => 'asc'
)
)
)
));
Passing an array instead of a string for order allows you to sort by multiple conditions. The above example would create the following ORDER SQL:-
ORDER BY Post.created ASC, Post.id ASC
You can also define a default ordering on the model itself which can be very useful:-
class Post extends AppModel {
public $order = array('Post.created' => 'asc', 'Post.id' => 'asc');
}

Related

Single Pagination on multiple models in cakephp

My requirement is to search on 2 different tables : Vets and Clinics. There might be relation between them i.e result should fetch clinics have 'a' in name and vets having 'a' in them. Vets might be related to clinics or might not. Currently I'm doing the following. Is there any method to avoid running 2 queries which can also help me use the cakephp pagination helper?
$this->paginate = array(
'Vet' => array(
'conditions' => $conditions,
'fields' => array('Vet.id', 'Vet.name', 'Vet.professionnal_address', 'phone_number', 'Vet.email', 'Vet.type', 'Vet.latitude', 'Vet.longitude','Vet.city','Vet.clinic_id','Vet.zipcode'),
'joins' => array(
array(
'table' => 'vet_appointment_types',
'alias' => 'VetAppointmentType',
'type' => 'LEFT',
'conditions' => array(
'Vet.id = VetAppointmentType.vet_id',
)
)
),
'limit' => $limit,
'group' => array(
'Vet.id'
),
'order' => array(
'Vet.name' => 'ASC'
)
),
'Clinic' => array(
'conditions' => $conditions1,
'fields' => array('Clinic.*'),
'limit' => $limit,
'order' => array(
'Clinic.name' => 'ASC'
)
)
);
$results = $this->paginate('Vet');
$results2 = $this->paginate('Clinic');
I tried by extending the default pagination component in a non database model and using union but the database structure in a bit complex so can't use union. Also, i think implementing a temporary table based model would be an option, but since it'll be used for searching, so how exactly to go about implementing it, I'm unable to think. Any help would be +1'd ;)
As per the suggestion, I accomplished it by using database views. Below is the code. Any improvement suggestions is highly appreciated.
// \app\Model\Search.php
App::uses('ConnectionManager', 'Model');
App::uses('AppModel', 'Model');
App::uses('CakeLog', 'Log');
class Search extends AppModel {
public function __construct() {
parent::__construct();
$connection = ConnectionManager::getDataSource('default');
$return = $connection->execute("CREATE OR REPLACE VIEW `search` AS
SELECT Vet.id, (CONCAT( `Vet`.`fname` , ' ', `Vet`.`lname` )) AS name, 'vet' AS TYPE , Vet.latitude, Vet.longitude, Vet.zipcode, Vet.speciality FROM `db`.`vets` AS `Vet`
UNION
SELECT Clinic.id, Clinic.name, 'clinic' AS TYPE , Clinic.lat, Clinic.long, Clinic.zipcode, Clinic.address FROM `db`.`clinics` AS `Clinic`");
$return = ($return == true)?'Search View created successfully on '.date('Y-m-d H:i:s'):$return;
CakeLog::write('search', $return);
}
public $useTable = 'search';// This model does not use a database table
public $primaryKey = 'id'; // Define primary key
public $useDbConfig = 'default'; // Define db
}
This is the model.Everytime user searches anything, when the model is loaded in controller, view is created/replaced so that updated values can be fetched. It can be used as any other model in cakephp. It also supports virtualFields as well. To build your query for the database view you can also use the query builder which i used as follows.
$joins = array(
array(
'table' => 'vet_appointment_types',
'alias' => 'VetAppointmentType',
'type' => 'LEFT',
'conditions' => array(
'Vet.id = VetAppointmentType.vet_id',
)
)
);
$dbo = $this->Vet->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('Vet.id', 'Vet.name'),
'table' => $dbo->fullTableName($this->Vet),
'alias' => 'Vet',
'limit' => $limit,
'group' => array(
'Vet.id'
),
'order' => array(
'Vet.name' => 'ASC'
),
'offset' => null,
'joins' => $joins,
'conditions' => $conditions
),
$this->Vet
);
$query = $subQuery;
$query .= ' UNION ';
$dbo = $this->Clinic->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('Clinic.id', 'Clinic.name'),
'table' => $dbo->fullTableName($this->Clinic),
'alias' => 'Clinic',
'limit' => $limit,
'conditions' => $conditions1,
'limit' => $limit,
'order' => array(
'Clinic.name' => 'ASC'
),
'offset' => null
),
$this->Clinic
);
$query .= $subQuery;
print_r($query);

cakePHP Left Join Main Model in hasMany Relationship Using $options Array causes error

Ok. This must be an easy one for you cakePHP ninjas out there! But my cakePHP skills are still pretty much at the lowest level. So I have 2 Models; Donor Model, and Donations Model.
The setup is as follows :
Donor Model hasMany Donation Model
Donor Model
public $hasMany = array(
'Donation' => array(
'className' => 'Donation',
'foreignKey' => 'donor_id',
'order' => 'Donation.created DESC',
'limit' => 10,
'dependent' => true
)
);
Now in the DonorsController I am using the paginatorComponent's paginate() method. Within my index I have this code
public function index($id = null){
$options['joins'] = array(
array(
'table' => 'donations',
'alias' => 'Donation',
'type' => 'LEFT',
'conditions' => array(
'Donor.id = Donation.donor_id',
))
);
$donors = $this->Paginator->paginate('Donor',$options);
$this->set('donors',$donors);
}
However this returns an sql Error :
'1054 Unknown column 'joins' in 'where clause' ''
Anyone knows why this is happening, Or if the above code is correct? thanks
Your code is incorrect, please see the below function:
public function index($id = null){
$options['joins'] = array(
array(
'table' => 'donations',
'alias' => 'Donation',
'type' => 'LEFT',
'conditions' => array(
'Donor.id = Donation.donor_id',
))
);
$this->paginate = $options;
$donors = $this->Paginator->paginate('Donor');
$this->set('donors',$donors);
}

How do I traverse CakePHP relations?

I'm trying to traverse the relations on CakePHP models.
This is the database:
I can access product attributes (product->attributes) on a product model but I cannot access the product attributes on Ad model (ad->product->attributes).
Here is my code:
//Product Model
class Product extends AppModel {
public $useTable = 'products';
public $displayField = 'name';
public $hasAndBelongsToMany = array(
'Attributes' =>
array(
'className' => 'Attribute',
'joinTable' => 'product_has_attributes',
'foreignKey' => 'products_id',
'associationForeignKey' => 'attributes_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'with' => 'product_has_attributes'
)
);
public $hasMany = array(
'Ads' => array(
'className' => 'Ad',
'foreignKey' => 'Products_id',
'conditions' => '',
'order' => '',
'limit' => '',
'dependent' => true
)
);
//Ad Model
class Ad extends AppModel {
public $displayField = 'Name';
public $belongsTo = array(
'Product' => array(
'className' => 'Products',
'foreignKey' => 'products_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
//Attribute Model
class Attribute extends AppModel {
public $displayField = 'name';
public $hasAndBelongsToMany = array(
'Products' =>
array(
'className' => 'Product',
'joinTable' => 'product_has_attributes',
'foreignKey' => 'attributes_id',
'associationForeignKey' => 'products_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'with' => 'product_has_attributes'
)
);
// Products controller -> Action View
class ProductsController extends AppController {
public function view($id = null) {
if (!$this->Product->exists($id)) {
throw new NotFoundException(__('Invalid product'));
}
$options = array('conditions' => array('Product.' . $this->Product->primaryKey => $id));
$this->set('product', $this->Product->find('first', $options));
}
}
// Ads controller -> Action View
class AdsController extends AppController {
public function view($id = null) {
if (!$this->Ad->exists($id)) {
throw new NotFoundException(__('Invalid ad'));
}
$options = array('conditions' => array('Ad.' . $this->Ad->primaryKey => $id));
$this->set('ad', $this->Ad->find('first', $options));
}
And here is what I do in the views:
//Products Views: snipet of view.ctp
print_r ($product);
// this prints rhe product and all associated attributes
//Ads Views: snipet of view.ctp
print_r ($ad['Product']);
//this will print only the product fields, but not the attributes associated to the product
What is wrong? How do I access the relation Ad->product->attribute from my Ad model?
I can think of a couple of simple ways to accomplish this.
First:
class AdsController extends AppController {
if (!$this->Ad->exists($id)) {
throw new NotFoundException(__('Invalid ad'));
}
$options = array(
'conditions' => array('Ad.' . $this->Ad->primaryKey => $id),
'recursive' => 1, // or more
);
$this->set('ad', $this->Ad->find('first', $options));
}
That would be the simplest code change to make sure you get attributes that are related to the product that is returned.
Otherwise, you alluded to this in your question. You can access related Models through the model, so:
$this->Ad->Product->find(...);
I have had issues with Cake not liking my find conditions when using related models in conditions, and I'm not sure of the proper syntax, but if you wanted to pursue that, I'm sure you can track it down through the docs or by experimentation.
Finally, I would advise you to check into the ContainableBehavior, which will allow you to fine tune which fields are actually returned in the results. I hope this answers your question!
Perhaps the problem could be that you're not using CakePHP's conventions.
From the docs:
"new join table’s name needs to include the names of both models involved, in alphabetical order, and separated with an underscore ( _ )."
So, your join table should be named attributes_products.
Also, check your foreign keys. They should be in singular form.
attributes_products.id
attributes_products.product_id
attributes_products.attribute_id
Hopefully that solves the problem.
References:
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm
http://book.cakephp.org/2.0/en/getting-started/cakephp-conventions.html#model-and-database-conventions

CakePHP paginator not paginating

I have a paginator as follows:
var $paginate = array(
'order'=>array('ReleaseServer.server_environment'=>'ASC',
'ReleaseServer.server_name'=>'ASC'),
'joins'=>array(
array(
'table' => 'release_server_to_components',
'alias' => 'ReleaseServerToComponent',
'type' => 'LEFT',
'foreignKey' => false,
'conditions'=> array('ReleaseServer.id = ReleaseServerToComponent.release_server_id')
),
array(
'table' => 'release_components',
'alias' => 'ReleaseComponent',
'type' => 'LEFT',
'foreignKey' => false,
'conditions'=> array('ReleaseServerToComponent.release_component_id = ReleaseComponent.id')
)
),
'group'=>array('ReleaseServer.id'),
'contain' => array(
'ReleaseServerToComponent' => array(
'ReleaseComponent' => array(
'Release'
)
)
),
'limit' => 25,
);
Then in my controller function I do the following:
$this->set('allServers', $this->paginate('ReleaseServer', $conditions));
Where $conditions are some extra conditions for the query.
As you see above I set the limit at 25.
There are 29 records in the database however, but the page only shows 25 and the page says there is only one page.
but when a person clicks on one of the column headers to order them, some rows that were not there before magically appear, and others disappear. Why would this be?
If you need any other info please let me know
UPDATE
Now i see that the problem resides in the group part of the paginate variable, but i need it in order to make it so I do not get multiple rows of the same thing.
How do I fix that?
I solved it by adding a new paginateCount() function to my model:
function paginateCount($conditions = null, $recursive = 0, $extra = array())
{
$count = $this->find('count', array(
'fields' => 'DISTINCT ReleaseServer.id',
'conditions' => $conditions
));
return $count;
}

CakePHP: Retrieving records based on field in related model

I'm trying to search based on a field in a related model. I can do so with the belongsTo model, but not the hasMany model. I'm sure this is something simple I'm overlooking but I can't see it. Could someone point me in the right direction (or even the cakebook page I need - searched, but didn't find anything that looked like this). Thanks
In groups controller: (this doesn't work)
$group = $this->Group->find('all', array('conditions' => array('Voucher.id' => '55')));
This does:
$group = $this->Group->find('all', array('conditions' => array('TourOperator.id' => '3')));
Background file snippets:
Group model:
var $belongsTo = array(
'TourOperator' => array(
'className' => 'TourOperator',
'foreignKey' => 'tour_operator_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
var $hasMany = array(
'Voucher' => array(
'className' => 'Voucher',
'foreignKey' => 'group_id',
'dependent' => true,
'conditions' => '',
'fields' => '',
'order' => 'Voucher.date, Voucher.meal_type_id'
)
);
Voucher model:
var $belongsTo = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Tour Operator model:
var $hasMany = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'tour_operator_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
Update (as posted in comment below, but clearer to read here)
I've ended up using this.
$groups = $this->Group->Voucher->find('all', array('fields' => 'DISTINCT group_id', 'conditions' => array('Voucher.status' => 'pending')));
$group_ids = Set::extract($groups, '/Voucher/group_id');
$data = $this->Group->find('all', array('conditions' => array('Group.id' => $group_ids)));
I get a distinct list of group IDs matching my criteria, create an array of just the IDs and then use that to pull the groups so that the arrays are ordered as I expect them in my view.
Can you recast your query to $this->Voucher->find, and then use the associated Group data returned from that query? As you say, there's no problem finding groups by their TourOperator id, and the Groups/Voucher relationship looks like it's the inverse of that.
With CakePHP's Containable behaviour you can't use conditions on related data in hasMany and hasAndBelongsToMany associations. This is because it uses seperate queries to get those associations.
With the Linkable behaviour, however, you can use data from hasMany associations to filter records. The behaviour can be downloaded here: https://github.com/Terr/linkable
Linkable uses standard JOINs to, well, join related data. This means it doesn't pull in all the related data (ie, all the Vouchers related to your Group(s)), just like a normal SELECT. However, it can work together with Containable to do both.
You can find examples of how Linkable works in the README.
(disclaimer: I currently maintain Linkable, but that is not the reason I point to it)

Categories