Problems returning result of CDbCriteria based query - yii

I have a query as follows
$criteria1 = new CDbCriteria();
$criteria1->condition = 'id = 1';
$modelA=Table1::model()->find($criteria1);
I can pass it to a view and return the title and entry
$this->widget('bootstrap.widgets.TbBox', array(
title' => $modelA['title'],
'content' => $modelA['entry'] ));
Now I'd like to return a range of entries
$criteria2 = new CDbCriteria();
$criteria2->condition = 'id > 7';
$modelB=Table1::model()->findAll($criteria2);
(btw : I'm following a form as laid out here). I was expecting to be able to read the resulting array of values out as below, but ['title'] is now being seen as a undefined index (obviously I'm expecting to read this out in a loop but you get the point)
$this->widget('bootstrap.widgets.TbBox', array(
'title' => $modelB['title'][0],
'content' => $modelB['entry'][0]));
Where am I going wrong?
Thanks

No, the indexes should be specified in the different order: the number of a specific element first, then the name of the property. Additionally, it's better (=cleaner) to name the result of findAll so it'll show you (and any other reader) that it's a collection, not a single model:
$models = Table1::model()->findAll($criteria2);
// ...
$this->widget('bootstrap.widgets.TbBox', array(
'title' => $models[0]['title']
//...
));
But even that's not necessary if you use foreach (and you probably will):
foreach ($models as $model):
// ...
$this->widget('some.name', array(
'title' => $model['title']
);
endforeach;

Related

Yii, CGridView: filtering and sorting count column from related tables

I have 3 tables: productions, chunks (production may have a lot of chunks, so chunks contains production_id column) and chunk_designers (chunk may have a lot of designers, so chunk_designers has a column chunk_id).
I have to render data from productions in CGridView and add a column: count of chunks (for every production item in table), which are not assigned to any chunk. In other words:
SELECT COUNT(*) FROM `chunks`
left JOIN `chunk_designers`
ON chunks.id = chunk_designers.chunk_id
WHERE production_id = 520 AND chunk_id IS null
...where 520 - dynamic value for every row, productions.id.
ChunkController action:
function actionProductionList() {
$model = new Productions('search');
$model->unsetAttributes(); // clear any default values
$dataProvider = $model->search($_GET);
$dataProvider->criteria->order = 'start_time DESC';
$this->render('production_list', array(
'provider' => $dataProvider,
'model' => $model,
));
}
Model Productions:
class Productions extends AppModel {
....
public $unassignedBlocksCount;
....
public function rules() {
return array(
array('unassignedBlocksCount, id, ...', 'safe'),
array('unassignedBlocksCount, id, ...', 'safe', 'on'=>'search'),
);
}
//in search() I added:
$criteria->compare('unassignedBlocksCount', $this->unassignedBlocksCount, true);
}
And in view:
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'menu-grid',
'dataProvider' => $provider,
'filter' => $model,
'enablePagination' => true,
'columns' => array(
array(
'name' => 'unassignedBlocksCount',
'value' => 'Chunks::getUnassignedBlocksCount($data->id)'
),
...
Column data is rendered fine, but when I try to filter it, I get the following error: unknown column unassignedBlocksCount in where clause.
I tried to write in search() method in Productions model:
// subquery to retrieve the count of posts
$count_sql = "(SELECT COUNT(*) FROM `chunks` "
. "LEFT JOIN `chunk_designers` "
. "ON chunks.id = chunk_designers.chunk_id "
. "WHERE production_id = 520 AND chunk_id IS NULL)";
// select
$criteria->select = array(
'*',
$count_sql . " as unassignedBlocksCount",
);
And just added unassignedBlocksCount element in columns in view.
But I still get the same error, when filter, and also problem is that I don't know, how to get production_id in $count_sql dynamically, depending on every table row (that's why in every row for that column value is 1, because it's correct value for production_id = 520).
So, my question is: how to get sortable and filterable column in CGridView, which is calculated (by COUNT()) from data in other tables? Thanks in advance.

how to integrate multimodelform with echmultiselect yii?

I am running into a little snag with combining extensions "EchMultiSelect" and "MultiModelForm" of YII framework.
What I'm trying to do is copy a set of form elements one of which elements is an EchMultiSelect widget.
According to tutorial on the jqRelCopy page, I would need to pass a copy of the element (datePicker in their example) to the 'jsAfterNewId' option:
'jsAfterNewId' => JQRelcopy::afterNewIdDatePicker($datePickerConfig),
So, I tried to modify that to:
'jsAfterNewId' => MultiModelForm::afterNewIdMultiSelect($memberFormConfig['elements']),
Where I also added the following to MultiModelForm.php:
public static function afterNewIdMultiSelect($element)
{
$options = isset($element['options']) ? $element['options'] : array();
$jsOptions = CJavaScript::encode($options);
return "if(this.attr('multiple')=='multiple'){this.multiselect(jQuery.extend({$jsOptions}));};";
}
its copied and working properly when i am using Add Person link but what happens if i am adding/cloning three items for example and when i change the third item multiselct option then its reflecting to the first multiselect dropdown only this is same for other as well also when i am adding new items by clicking on the Add Person link then its cloning the same element to the new row item
here is the code for the form configuration variables and multimodel widget call.
//$userList=array of the userIds from users table
$memberFormConfig = array(
'elements'=>array(
'userId'=>array(
'type'=>'ext.EchMultiSelect.EchMultiSelect',
'model' => $User,
'dropDownAttribute' => 'userId',
'data' => $userList,
'dropDownHtmlOptions'=> array(
'style'=>'width:500px;',
),
),
...
...
));
calling the MultiModelForm widget from the same view file
$this->widget('ext.multimodelform.MultiModelForm',array(
'id' => 'id_member', //the unique widget id
'formConfig' => $memberFormConfig, //the form configuration array
'model' => $model, //instance of the form model
'tableView' => true,
'validatedItems' => $validatedMembers,
'data' => Person::model()->findAll('userId=:userId', array(':userId'=>$model->id)),
'addItemText' => 'Add Person',
'showAddItemOnError' => false, //not allow add items when in validation error mode (default = true)
'fieldsetWrapper' => array('tag' => 'div',
'htmlOptions' => array('class' => 'view','style'=>'position:relative;background:#EFEFEF;')
),
'removeLinkWrapper' => array('tag' => 'div',
'htmlOptions' => array('style'=>'position:absolute; top:1em; right:1em;')
),
'jsAfterNewId' => MultiModelForm::afterNewIdMultiSelect($memberFormConfig['elements']),
));
Can someone help me with this please?
Thanks in advance!
After a long searching and googleing i found the solution for this, just replace the function in your MultiModelForm.php:
public static function afterNewIdMultiSelect($element)
{
$options = isset($element['options']) ? $element['options'] : array();
$jsOptions = CJavaScript::encode($options);
return "if ( this.hasClass('test123456') )
{
var mmfComboBoxParent = this.parent();
// cloning autocomplete and select elements (without data and events)
var mmfComboBoxClone = this.clone();
var mmfComboSelectClone = this.prev().clone();
// removing old combobox
mmfComboBoxParent.empty();
// addind new cloden elements ()
mmfComboBoxParent.append(mmfComboSelectClone);
mmfComboBoxParent.append(mmfComboBoxClone);
// re-init autocomplete with default options
mmfComboBoxClone.multiselect(jQuery.extend({$jsOptions}));
}";
}
Thats It....!!
Thanks...!!!

How to SORT a SET and GET the full HASH

I'm new to Redis and I have to say I love it till now :)
I'm bumping into an issue I'm not sure how to solve it in the more efficient way.
I have a SET of HASH. Each HASH describe a post.
Here is the code to create and store the HASH:
// Create the HASH
$key = 'post:'.$post->getId();
$this->redis->hSet($key, 'created', $post->getCreated());
$this->redis->hSet($key, 'author', $post->getAuthor());
$this->redis->hSet($key, 'message', $post->getMessage());
// Store the HASH in the SET
$this->redis->sAdd('posts', $post->getId());
Now, previously I was storing all the post's attributes in a data field of the HASH (json_encoded) and I was fetching the information like this:
$key = 'posts';
$data = $this->redis->sort($key, array(
'by' => 'nosort',
'limit' => array($offset, $limit),
'get' => 'post:*->data '
));
if (!is_array($data)) {
return array();
}
foreach ($data as &$post) {
$post = json_decode($post, true);
}
It was working great, I had all the posts information :)
But I had conflicts when updating the post in Redis (concurrent updates), so I've decided to have all the post's attributes in separated fields of the HASH and it fixed my issue of conflicts.
Now the problem I have is to fetch the HASH from my SET. Do I have to specify every single fields like this:
$key = 'posts';
$data = $this->redis->sort($key, array(
'by' => 'nosort',
'limit' => array($offset, $limit),
'get' => array('post:*->created', 'post:*->author', 'post:*->message')
));
Or is there another way to fetch the full HASH directly within the SET?
I heard about pipeline but I'm not sure it's what I'm looking for and if I can use it with phpredis
Cheers, Maxime
UPDATE
I'm not sure I explained myself clearly. I have some elements in a set (post_id).
I want to get the first 10 posts of the SET, which means I want 10 hash (with all their fields and value) in order to build a post object.
I was previously storing all the object information in one field of the hash (data), now I have one field per attribute of the object.
before:
myHash:<id> data
now:
myHash:<id> id "1234" created "2010-01-01" author "John"
Before I was using SORT to fetch the top 10 posts (and paginate easily), like this:
$key = 'posts';
$data = $this->redis->sort($key, array(
'by' => 'nosort',
'limit' => array(0, 10),
'get' => 'post:*->data '
));
Now that I have X members to my hash I'm wondering what is the best solution.
Is it:
$key = 'posts';
$data = $this->redis->sort($key, array(
'by' => 'nosort',
'limit' => array($offset, $limit),
'get' => 'post:*->data '
));
Or maybe:
$key = 'posts';
$data = $this->redis->sort($key, array(
'by' => 'nosort',
'limit' => array($offset, $limit),
'get' => '#'
));
foreach($data as $post_id) {
$posts[] = $this->redis->hGetAll('post:'.$post_id);
}
Or finally:
$key = 'posts';
$data = $this->redis->sort($key, array(
'by' => 'nosort',
'limit' => array($offset, $limit),
'get' => '#'
));
$pipeline = $this->redis->multi();
foreach ($data as $post_id) {
$pipeline->hGetAll('post:'.$post_id);
}
return $pipeline->exec();
Or something else that I don't know yet?
What is the best, faster way to do this?
If you have read redis's source , you'll find that is not possible. There is a workaround that using lua script to combine 'sort' and 'hgetall' commands in a single redis invocation.
The 'get pattern' is processed by function 'lookupKeyByPattern'.
https://github.com/antirez/redis/blob/unstable/src/sort.c#L61
If you start with the redis.io documentation on hashes you'll find there are commands which allow you to get multiple hash members. In particular "HGETALL" for pulling all fields and values, or "HMGET" for pulling a set of fields with their values.
Additionally, for setting them I would recommend setting them in one pass with "HMSET"

Use column alias in dataProvider

In my model I have a relation() like this.
'notRelUser' => array(
self::HAS_MANY,
'LocationUser',
'location_id',
'condition' => 'notRelUser.status is null',
'on' => 'notRelUser.user_id = ' . Yii::app()->user->getId(),
'with' => array('parent_location'),
'select' => array('*', 'name AS canApply'),
),
and this
public $canApply;
In my controller I have this
$regions = Location::model()->with('notRelUser')->findAll();
$arrayCanApply = new CArrayDataProvider($regions);
I then am trying to print out the value of canApply in a widget for datagrid
<?php $this->widget('bootstrap.widgets.TbGridView', array(
'dataProvider'=>$arrayCanApply,
'columns'=>array(
array('name'=>'name', 'header'=>'Name'),
array('name' => 'canApply', 'value'=>'$data->canApply', 'header'=>'CanApply'),
),
));
But the column with canApply is empty.
I have also tried without the $data-> part.
How can I print this alias?
(I know the value of the alias i trivial, but I will change that later)
UPDATE:
I can get the value by using select in CDbCriteria - but I want to do it in relation.
This does work.
$criteria = new CDbCriteria;
$criteria->select = '("foobar") AS canApply';
$regions = Location::model()->with('notRelUser')->findAll($criteria);
but it seems odd that I have to create a $criteria on everytime
you need to declare can canApply as an attribute of the model
in your model decalare canApply as an attribute
public $canApply;
I also had the same problem. Try 'value'=>'$data->notRelUser->canApply'
instead of 'value'=>'$data->canApply'. Also, you need to declare attribute
public $canApply;
in your LocationUser model, not in Location model.

Pagination with hasMany association cakePHP

I have two tables:
Contestant and Votes
Contestant hasMany Votes
I've tried doing a count(Vote.id) as Votes so I can place it on the
recordset and just paginate them but I have no idea where to place it.
I did it on the fields array but it gave me the total count of votes
regardless of the contestant they belong to.
The votes are linked together in the Contestant recordset so what I
did was on my view I did a count($contestant[Vote]) but this can't be
paginated and my client wants to be able to sort the contestants by
votes.
Is there a way I can do something like this on my view?:
sort('Votes', 'count(Vote)'); ?>
Or do I have to create a query which does a count for all the votes
where Contestant.id = Votes.contestant_id ?
Controller Contestant:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$this->paginate =
array(
'order' => 'id ASC',
'contain' => array(
'Vote' => array(
'fields' => array("Vote.contestant_id",'Vote.id')
)
)
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate('Contestant',
$conditions));
}
Check out deceze's response in this question: CakePHP mathematic-calculation field?
Essentially you want to do something like this I'm guessing:
'contain' => array(
'Vote' => array(
'fields' => array('SUM(Vote.id) AS Contestant__votes'),
'group' => array('Vote.contestant_id'),
)
)
Since cakephp doesn't support group by in containable behavior I tried a different approach. Create the paginate var for the vote model instead (All of this is done in the Contestants Controller):
var $paginate = array(
'Vote'=>array(
'limit'=>5,
'fields' => array(
'Contestant.*, count(Vote.contestant_id) as Contestant_votes, Vote.id'
),
'group' => array(
'Vote.contestant_id'
),
'order' => array(
'Contestant_votes Desc'
)
),
'Contestant'=>array(
'limit'=>5,
'order' => array(
'Contestant.id Desc'
)
)
);
And now in my controller I do the following:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate($this->Contestant->Vote,$conditions));
}
Now the contestants are ordered by their total vote tally, although I still can't figure how to place the Contestant_votes as a paginator variable since in the record set it's in a array of it's own and not in any of the model arrays used to paginate.
Thanks Matt Huggins your approach was the one that led me to this solution.
Addition: Do you also want to sort by Votes (total votes) ascending or descending? If yes, you can not do it easily by the default pagination method of cakephp.
For that you need a little tweak. Here is the details about this trick: CakePHP Advanced Pagination – sort by derived field
Hope you'd find it helpful.
Thanks
Adnan
For the specific relationship you define, your needs are well-served by counter-caching.
You will need to define a new field in your contestants table: vote_count. Then, in your Votes model, you'll need to update the $belongsTo definition slightly:
class Votes extends AppModel
{
var $belongsTo = array(
'Contestant' => array( 'counterCache' => true )
);
}
Now, anytime a Vote record is saved, the vote_count field of the parent Contestant will be updated. Now you can simply sort by Contestant.vote_count as you would any other Contestant field:
class ContestantsController extends AppController
{
// Controller stuff that comes before...
function index()
{
$this->paginate = array( 'Contestant' => array(
'conditions' => array( 'Contestant.active' => 1 ),
'order' => array( 'Contestant.vote_count' => 'DESC' ),
));
$contestants = $this->paginate('Contestants');
$this->set( compact('contestants'));
}
// Controller stuff that comes after...
}