Guys help me to do this. I'm new to YII. I want to display each item branches stock like this.
actual database
What is the best way to do this?
What you are looking for is a cross tab or pivot table. Here is a link: http://www.artfulsoftware.com/infotree/qrytip.php?id=523
I have been looking for the same thing and have a solution using CSqlDataProvider. It is important to note when using CSqlDataProvider it returns an array unlike CActiveDataProvider which returns an object.
Here is the model code
public function displayStock($id)
{
$sql='SELECT product_id, COUNT(*) as productCount FROM stock WHERE assigned_to = 2 GROUP BY product_id';
$dataProvider=new CSqlDataProvider($sql, array(
'pagination'=>array(
'pageSize'=>10,
),
));
return $dataProvider;
}
Here is the code for the veiw
$stockDataProvider = Stock::model()->displayStock(Yii::app()->user->id);
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'stock-grid',
'ajaxUpdate' => true,
'template'=>'{items}{summary}{pager}',
'dataProvider'=>$stockDataProvider,
'columns'=>array(
'product_id',
array(
'name'=>'test',
'value'=>'$data["productCount"]'
),
array(
'class' => 'CButtonColumn',
'template' => '{view} {update}',
),
),
));
When defining the SQL statement in the model, you can see we have set the COUNT(*) to be called productCount. Then in the view we reference that name in the columns array like so
array(
'name'=>'test',
'value'=>'$data["productCount"]'
),
So simply replace productCount with the name you have used.
I'm sure the above code could do with some tweaking ie using Binded params in the query etc, but this has worked for me.
Any comments or improvements are very welcome. I'm only about 8 months into php and 3 months into yii.
Related
I have the following code in my projects controller's index action
I achieved it via custom queries
1- it is good to use custom queries?
2- how can I write the following queries using cakephp functions joins contains etc
3- which method will be good?
my tables are as below
portfolio_projects [id,name,....,user_id,job_id,manymore_id]
portfolio_images [id,project_id,image,orders,status,.....]
portfolio_tags [id,tag,....]
portfolio_project_tags[id,project_id,tag_id]
and query is as below. I did this to fetch only needed data that is projects with its images and tags but not project's (user,job and others)
there are other tables linked to tags, images tables too but I do not need that data here.
$this->Project->recursive = -1;
$projects = $this->Project->find('all',array(
'conditions' => array(
'Project.user_id' => $this->Auth->user('id')
)
));
foreach($projects as $key=>$project)
{
//fetching project related tags starts here
$q="select * from portfolio_project_tags where 0";
$q="select tag.id as id,tag.tag from portfolio_project_tags as p left outer join portfolio_tags as tag on p.tag_id=tag.id where p.project_id=".$project['Project']['id'];
$tags = $this->Project->query($q);
$projects[$key]['Project']['tags']=$tags;
//fetching project related tags ends here
//fetching project related images starts here
$q2="select * from portfolio_images where 0";
$q2="select img.id as id,img.title as title, img.image as image from portfolio_images as img where img.project_id=".$project['Project']['id']." and img.status='Publish' order by orders";
$snaps = $this->Project->query($q2);
$projects[$key]['Project']['snaps']=$snaps;
//fetching project related images ends here
}
$this->set('projects',$projects);
You have 3 possibilities (at least) to retrieve data from a join table in CakePHP, that you should consider in the following order:
Containable Behaviour
CakePHP join query
Custom query
In your example, only the q1 request need a join because the q2 can be express as:
$this->PorfolioImage->find('all', array(
'fields' => array('PortfolioImage.id, PortfolioImage.title, PortfolioImage.image'),
'conditions' => array(
'PortfolioImage.project_id' => $project['Project']['id']
)
));
The containable behaviour
In your Project model, you could add information to the related models, such as:
class Model extends AppModel {
public $actAs = array('Containable');
public $hasMany = array('PortfolioImage');
public $belongsTo = array('PortfolioTag') ;
}
Then, when you retrieve a Project, you have access to the related PortfolioImage and Tag, for example:
debug($this->Project->read(null, 1)) ;
Would output:
Array(
'Project' => array(
'id' => 1,
/* other fields */
),
'PortfolioTag' => array(
'id' => /* */,
'tag' => /* */
),
'PortfolioImage' => array(
0 => array(
'id' => /* */,
/* other fields */
),
1 => array(/* */)
)
)
You just need to be carefull on the naming of foreign key, the CakePHP documentation about relationship is really easy to read.
CakePHP join query
You can do a join query in CakePHP find method:
$this->Project->find('first', array(
'fields' => array('Tag.id', 'Tag.title'),
'joins' => array(
'table' => 'portfolio_tags,
'alias' => 'PortfolioTag',
'type' => 'LEFT OUTER',
'conditions' => array('PortfolioTag.id = Project.tag_id')
)
)) ;
I'm really not used to CakePHP join queries (the Containable behaviour is often sufficient), but you'll find lot of information in the CakePHP documentation.
Custom queries
You should really not use custom queries except if you really know what you're doing because you have to take query of almost anything (sql injection at first place). At least, when doing custom queries, use Prepared Statements (see information at the end of this link).
I'm trying to create an AJAX form whereby the content of a select field populates based on the choice of a preceding select field (you see this a lot with 'country' populating 'state/province'). In my case, I want users to be able to choose their province only if active accounts exist in it.
The Javascript I can write no problem. Fetching the data is where I'm... not so much stuck as doing too much work. CakePHP likes to build select fields with options in an array of the form
$options = array(select_option_value => display_text)
My strategy, though functional, must be more convoluted than cake intended (this a is segment of a controller method).
$provinceData = $this->Account->find('all',array('recursive' => 0,
'joins' => array(
array(
'table' => 'provinces',
'type' => 'LEFT',
'conditions' => array('Account.province_id = provinces.id')
)),
'fields'=>array('provinces.id', 'provinces.name', 'provinces.abbrev'),
'conditions' => array('registration > 2')));
$provinces = array();
foreach($provinceData as $pd) {
/*note: lowercase, plural below b/c can't get 'alias' => 'Province'
to work in joins array above : ( */
$id = $pd['provinces']['id'];
$name = $pd['provinces']['name'];
$provinces[$id] = $name;
}
$this->set(compact('provinces'));
Can anyone point out a more appropriate way to do this? I assume there must be a MySQL query that can do this, but I'm pretty bad at writing elaborate MySQL queries in the first place, let alone via Cake's convention (and, for you MySQL gurus out there, I'm happy to do this from a Model->query(//MySQL code) call instead!
Any and all help truly appreciated.
Assuming the relationship Account belongsTo Province you can try this code:
$accounts = $this->Account->find(
'all',
array(
'fields' => array('Account.province_id', 'Province.name'),
'conditions' => array('Account.registration > 2'),
'group' => 'Account.province_id'
)
);
$provinces = Hash::combine($accounts, '{n}.Account.province_id', '{n}.Province.name');
$this->set(compact('provinces'));
edit: missed bracket and a period instead of an underscore . Now should work
I'm not savvy with MySQL or databases generally, so here's a model of my data (table{cols}]) in order to make my question coherent:
Domains{id, name} Note: 'domains' here does not refer to web domains
Subdomains{id, domain_id, name}
Items{id, subdomain_id, name}
SubdomainsItems{id, subdomain_id, item_id} no domain_id column!
My Items Controller has a function, fetchWithin($domains, $subdomains) which, ultimately, should just execute one of two complexish find(). It's the complexish I can't get past.
Programmatically I can achieve this, but I'm quite certain the better way is by clever joins and the like. Alas, currently this is approach:
If $domainsis empty, do only steps 2&3, otherwise:
foreach($domains as $d): get all the rows of Subdomains where Subdomain.domain_id = Domains.id as $subdomains
foreach($subdomains as $s) : go get all the rows of SubdomainsItems where SubdomainsItems.subdomain_id = Subdomains.id as $item_ids
foreach($items_ids as $i): get all the rows of Items where Items.id = SubdomainsItems.items_id
This works, but I think this is obviating the power of a relational database and I'd like to understand how this should be done (ie. according to either Cakephp convention or simply by whatever MySQL statement would achieve this).
Help would be hugely appreciated, I try to learn the more complex aspects of SQL but it just goes right over my head. :S
Understanding the necessary query
With the structure described in the question the kind of query necessary is of the form:
SELECT
*
FROM
items
LEFT JOIN
subdomains ON (
items.subdomain_id = subdomains.id
)
LEFT JOIN
domains ON (
subdomains.domain_id = domains.id
)
WHERE
domains.name = "foo"
AND
subdomains.name IN ('some', 'list', 'of', 'subdomains');
Compared to the logic in the question this joins all three tables together and permits finding all items by domain name, or subdomain name (or any other criteria involving any or all three tables); Generally speaking if you want to find data in a db and use more than one query to get it - there's a more efficient way to do it.
Implementing the find call
There are a number of ways of creating such a query with Cake. The simplest, probably, is to use the join key and just specify the joins explicitly:
function fetchWithin($domains = null, $subdomains = null) {
$params = array(
'joins' => array(
array('table' => 'subdomains',
'alias' => 'Subdomain',
'type' => 'LEFT',
'conditions' => array(
'Subdomain.id = Item.subdomain_id',
)
),
array('table' => 'domains',
'alias' => 'Domain',
'type' => 'LEFT',
'conditions' => array(
'Domain.id = Subdomain.domain_id',
)
)
)
);
if ($domains) { // single value or an array
$params['conditions']['Domain.name'] = $domains;
}
if ($subdomains) { // single value or an array
$params['conditions']['Subdomain.name'] = $subdomains;
}
return $this->find('all', $params);
}
I am not very good with DB queries. And with Yii it's more complicated, since I am not very used to it.
I need to optimize a simple query
$userCalendar = UserCalendar::model()->findByAttributes(array('user_id'=>$user->id));
$unplannedEvents = CalendarEvent::model()->findAllByAttributes(array('calendar_id'=> $userCalendar->calendar_id,'planned'=>0));
CalendarEvent table, i.e the second table from which I need records does not have an user_id but a calendar_id from which I could get user_id from UserCalendar, i.e. the first table hence I created a UserCalendar object which is not a very good way as far as I understand.
Q1. What could I do to make it into one.
Q2. Yii does this all internally but I want to know what query it built to try it seperately in MySQL(phpMyAdmin), is there a way to do that?
Thanks.
Q1: You need to have the relation between UserCalendar and CalendarEvent defined in both of your active record models (in the method "relations").
Based on your comments, it seems like you have the Calendar model that has CalendarEvent models and UserCalendar models.
Lets assume your relations in Calendar are:
relations() {
return array(
'userCalendar' => array(self::HAS_MANY, 'UserCalendar', 'calendar_id'),
'calendarEvent' => array(self::HAS_MANY, 'CalendarEvent', 'calendar_id'),
}
In CalendarEvent:
relations() {
return array( 'calendar' => array(self::BELONGS_TO, 'Calendar', 'calendar_id'), );
}
And in UserCalendar:
relations() {
return array( 'calendar' => array(self::BELONGS_TO, 'Calendar', 'calendar_id'), );
}
So to make the link between UserCalendar and CalendarEvent you'll need to use Calendar
$criteria = new CDbCriteria;
$criteria->with = array(
"calendarEvent"=>array('condition'=>'planned = 0'),
"userCalendar"=>array('condition'=> 'user_id =' . $user->id),
);
$calendar = Calendar::model()->find($criteria);
and $calendar->calendarEvent will return an array of calendarEvent belonging to the user
Q2: you can enable web logging so all the db request (and others stuffs) will appear at the end of your page:
Logging in Yii (see CWebLogging)
In your application configuration put
'components'=>array(
......
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CWebLogRoute',
),
),
),
),
I have three models linked in this manner: Item->Order->Payment
Order hasMany Item
Order hasOne Payment
Now, I am paginating Items and want to add a condition in it to find only items of that order which has payment of a particular id. I hope that makes sense :P
I added the condition as:
array('Payment.id'=>$id)
but it doesn't work. Obviously cause Payment is not associated with Item.
So, how can I go about this?
I am new to cakephp, maybe I am completily wrong but as I understand it you can use other models in your controller with the $uses variable. First make a query on payment model to get your order id, than you can use this id to find the corresponding items.
$uses=array('Item','Order','Payment');
$order_id=$this->Payment->find('first',array('fields'=>'order_id','conditions'=>array('id'=>$payment_id)));
$items=$this->Item->find('all',array('conditions'=>array('order_id'=>$order_id)));
I hope it help.
Why don't you add a condition:
array('Order.payment_id'=>$id)
I think this should work.
If you specify that you want two levels of recursion this should work. Im assuming you have
in Payment.php
//recursion level 1
var $belongsTo = array('Order');
in Order.php
//recursion level 2
var $hasMany = array('Items')
You are right that for paginate to work you must query the model you wish to page and sort the lists by.
in PaymentController.php
//Query two levels deep, so the $payment['Order']['Item'][0-n] will be present
var $paginate = array('recursive' => 2);
Note this method does generate another query for each row to retrieve items.
Make sure the debug level in app/config/core.php is set to 2 to see the database calls.
1) You can use Containable behaviour, in which case you need to put this in your Item model:
var $actsAs = array('Containable');
and this into your Items controller:
$items = $this->Item->find('all'
, array (
'contain' => array('Order' => array('Payment'))
, 'conditions' => array('Payment.id' => $paymentId)
)
)
However I suspect that that will do a left join onto the Payments table (as its a hasMany relationship). So you won't filter Items in any way.
2) If you can't get contains to work then I often use explict joins (read this bakery article by nate on joins) in my find queries. So in your Items controller you'd have:
$items = $this->Item->find('all'
, array (
, 'joins' => array(
array(
'table' => 'payments'
, 'alias' => 'Payment'
, 'type' => 'INNER'
, 'conditions' => array(
'Option.id = Payment.option_id'
)
)
)
, 'conditions' => array('Payment.id' => $paymentId)
)
)
You may also need to specify the join onto the options table.