Single Pagination on multiple models in cakephp - php

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);

Related

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

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');
}

Join single item from related table based on min of field

I have an Item model which has the following associations:
public $hasOne = array(
'Project' => array(
'className' => 'Project',
'foreignKey' => 'item_id'
)
);
public $hasMany = array(
'ItemPic' => array(
'className' => 'ItemPic',
'foreignKey' => 'item_id',
'dependent' => false
)
);
I am wanting custom data for different views of Item. It seems like CakePHP automatically includes Project data (maybe because it is hasOne?) and does not include the ItemPic data. In the index I really don't even want the Project data... however, I do want the ItemPic data. For each Item record pulled, I want a single ItemPic record joined to it. This ItemPic should be basically ItemPic.item_id = Item.id and ORDER BY ItemPic.rank LIMIT 1.
The purpose of this is basically so that in the index I can show a list of Items and a picture associated with each item. I would like all of the images along with the Project data in the view for a single Item, but not in the list/index.
I was told I could use containable like this:
// In the model
public $actsAs = array('Containable');
// In the controller
$this->paginate = array(
'conditions' => $conditions,
'contain' => array(
'ItemPic' => array(
'fields' => array('file_name'),
'order' => 'rank',
'limit' => 1
)
)
);
The above actually works how I want... however, I was also told that doing this would cause an extra query to be ran for every single Item... which I feel I should avoid.
I tried doing this, but I get duplicate data and it doesn't attach any ItemPic data:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'table' => 'item_pics',
'alias' => 'ItemPic',
'type' => 'LEFT',
'conditions' => array(
'ItemPic.item_id = Item.id'
),
'order' => 'rank ASC',
'limit' => 1
)
)
);
$paginated = $this->Paginator->paginate();
Can you please try this:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'table' => 'item_pics',
'alias' => 'ItemPic',
'type' => 'LEFT',
'conditions' => array(
'ItemPic.item_id = Item.id'
),
'order' => 'rank ASC',
'limit' => 1
)
),
'fields' => array('Item.*','Project.*','ItemPic.*')
);
In the fileds section you may or may not assign "Item" , "Project" according to your requirment.
Thanks

CakePHP: Find "Groups" where the "Members" of that group are not the current authorized user

I have a Groups model and a Users model.
Groups have and belong to many users.
Users have and belong to many groups.
I'm trying to create a result set of groups where the group users do not include the id of the currently logged in user. I've tried to use a conditional in the "contain" parameter and I've tried to do it using an inner join but the results always come up with groups that the user currently belongs to.
Short of doing my own custom MySQL query, I was wondering if there was a way I could do it using the CakePHP models exclusively.
Examples of what I have tried:
$otherMatches = $this->Group->find('all', array('joins' => array(array('table' => 'cake_users_groups', 'alias' => 'GroupsUsers', 'type' => 'inner', 'conditions' => array('GroupsUsers.group_id !=' => $groupId, 'GroupsUsers.user_id !=' => $this->Auth->user('id')))), 'limit' => 10, 'order' => array('rand()')));
and
$otherMatches = $this->Group->find('all', array(
'contain' => array(
'User' => array(
'UsersGroup' => array(
'conditions' => array('UsersGroup.user_id !=' => $this->Auth->user('id));
)
)
)
), 'limit' => 10, 'order' => array('rand()')));
Could anyone give me any suggestions?
Thanks in advance.
Solved it using a sub query:
$conditionsSubQuery['`UsersGroup`.`user_id`'] = $this->Auth->user('id');
$db = $this->UsersGroup->getDataSource();
$subQuery = $db->buildStatement(
array(
'fields' => array('`UsersGroup`.`group_id`'),
'table' => $db->fullTableName($this->UsersGroup),
'alias' => 'UsersGroup',
'limit' => null,
'offset' => null,
'joins' => array(),
'conditions' => $conditionsSubQuery,
'order' => null,
'group' => null
),
$this->UsersGroup
);
$subQuery = ' `Group`.`id` NOT IN (' . $subQuery . ') ORDER BY RAND() LIMIT 10 ';
$subQueryExpression = $db->expression($subQuery);
$conditions[] = $subQueryExpression;
$otherMatches = $this->Group->find('all', compact('conditions'));

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);
}

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;
}

Categories