Yii: Getting distinct field values from a related model using scopes - php

I'm working on the Admin view in Yii for my Project model.
One of the columns with a filter is user (owner) of the project.
I can do something like this:
'filter'=> CHtml::dropDownList('Project[user_id]', $model->user_id,
CHtml::listData(User::model()->orderAsc()->findAll(),
'id','username'),array('empty'=>'(select)')),
Which gives me a list of all users in the user table, but I'd like to create something that pulls the distinct users who own a project (I've got 200 users, but only a handful tend to create projects).
Project and User are related via:
'user' => array(self::BELONGS_TO, 'User', 'user_id')
I've tried a bunch of option in the findAll() method, but none worked, I'm not trying to do something with a scope.
So far I've tried this:
'filter'=> CHtml::dropDownList('Project[user_id]', $model->user_id,
CHtml::listData(Project::model()->with('user')->ownerUsernames()->
findAll(), 'id','username'),array('empty'=>'(select)')),
and my Scope is defined as:
'ownerUsernames' => array(
'with' => 'user.username',
'select' => 'user.username',
'distinct' => true,
),
I've tried so many iterations of the above I've lost count 'with'=>'username' 'select'=>'username' etc., but just can't seem to get it to work.
I've tried replace Project::model() with $model just because I thought it might have something to do with it, but no luck.
Any ideas are appreciated. Very new to Yii, but this seems like something it can do.

You have everything ready. Define for the project model a getter function like
public function getUsername()
{
return $this->user->name;
}
Now you should be able to use
CHtml::dropDownList('Project[user_id]', $model->user_id,
CHtml::listData(Project::model()->with('user')->ownerUsernames()->
findAll(), 'id','username'),array('empty'=>'(select)'))
The logic is that CHtml::listData will get the projects as a model, it will create the keys using $project->id and it will create the values using $project->username. Because you created the getted function it will know what $project->username is. Unfortunately CHtml::listData(Project::model()->with('user')->ownerUsernames()->findAll(), 'id','user.name') will not work because it cannot execute 'user.name' or anything like that.

actually you can do many things to accomplish that but some methods would be not appropriate due to time consumption. I prefer to tackle this problem as
Make a table in DB named user_project with attributes
id
user_id(fk to user table)
project_id(fk to project table)
When you create a project then also populate user_project with given fields.
Make its model and you will see relations in it. Now make a function like
public function getName()
{
return user->name;//here i have assumed that the relation name is user
}
Now query this table like
$user=UserProject::mode::findAll();
$list=CHtml::listData($user,'user_id','name');
and use this list to populate the dropDownList. The benefit of this method is that you dont need to query all users of the user table.

Related

cakephp 4.x query to get results from database

I came across a problem recently that I have to make a find(all) query in cakephp.
Problem is I was unable to call model function to cakephp controller & add a command in function that would allow me to make a find(all) function in cakephp and retrieve all rows in a database that I was looking for.
I would like to make a simple query to make a call to database table that is in database that retrieves rows in table that are matching query.
public function display(){
$query = $HouseParty->find('all', [
'conditions' => ['HouseParty.popular >' => 0],
'contain' => ['title', 'image', 'popular'],
'limit' => 6
]);
$results = $query->all();
var_dump($results);
}
Also what is code to make call to model in Cakephp Controller. So controller knows that we are making calls to models from controller. So we can interact with models from controller because I intend to make multiple calls to different models from same controller in future. Please be helpful looking for a response been stuck on this problem for days just making a question here that is relevant & resourceful for others as well. I would like to make a question come handy to other community members I would just need to get code apart from what I have mentioned above that what code should I write in order to make a call for calling models in one controller - call to multiple models from one controller. Is the above code correct and is so what makes it from giving an error as below. So what is code to make calls to models from database.
Error i get is below:
Call to a member function find() on null
I think you should start with a simple query based on your model.
I will assume your model is HouseParties (a database table named house_parties).
Then in your HousePartiesController, you can query:
$query = $this->HouseParties->find('all', [
'conditions' => ['popular >' => 0],
'limit' => 6
]);
I think you may be misusing 'contain' as a select. 'contain' is for related tables, not fields in the table you are querying.
If you go through the CMS tutorial in full https://book.cakephp.org/4/en/tutorials-and-examples.html it should be clear.

Yii1 search() by many-many relation returns only one related model

I have 3 tables:
clients, traders and client_trader_relation
clients can have many traders, and traders can have many clients so this is a MANY-MANY relationship with a "pivot" table. The relation is defined in clients model like this:
$relations = array(
'traders' => array(self::MANY_MANY, 'traders', 'client_trader_relation(client_id, trader_id)'),
);
Now everything works correctly when displaying a listing of all clients in let's say CGridView, but I also want to be able to search for clients by a specific trader (so if one of the traders is let's say id 10, then return this client).
I have done it like this in model's search() function:
public function search()
{
$criteria=new CDbCriteria;
$criteria->with = 'traders';
$criteria->together = true;
$criteria->compare('traders.id', $this->search_trader);
}
search_trader is an additional variable added to the model & rules so it cna be used for searching.
While this works, it successfully returns all clients of specified trader, the result doesn't contain any other related traders, just the one I'm searching for. I can understand this behaviour, because that's the way the generated SQL works.
I'm curious though if there is any way to return all the traders from such search without having to make any additional queries/functions? If not, then what would be the correct way of doing such thing? As for now, I can only think of some function in the model like getAllTraders() that would manually query all the traders related to current client. That would work, I could use this function for displaying the list of traders, but it would produce additional query and additional code.
You can use this to disable eager loading:
$this->with(['traders' => ['select' => false]]);
But this will create separate query for each row, so with 20 clients in GridView you will get extra 20 queries. AFAIK there is no clean and easy way to do this efficiently. The easiest workaround is to define additional relation which will be used to get unfiltered traders using eager loading:
public function relations() {
return [
'traders' => [self::MANY_MANY, 'traders', 'client_trader_relation(client_id, trader_id)'],
'traders2' => [self::MANY_MANY, 'traders', 'client_trader_relation(client_id, trader_id)'],
];
}
And then define with settings for eager loading:
$this->with([
'traders' => ['select' => false],
'traders2',
]);
Then you can use $client->traders2 to get full list of traders.
You can also define this relation ad-hoc instead of in relations():
$this->getMetaData()->addRelation(
'traders2',
[self::MANY_MANY, 'traders', 'client_trader_relation(client_id, trader_id)']
);

Laravel (OOP), model specfic array of values (for example titles of users)

I have model User and inside there is a field named title.
Now I want to define all possible titles. For example an array of
['0' => 'Mr.', '1' => 'Ms.']
What is the correct way to define these.
My idea is to create the public function titles() { return ['0' => 'Mr.', '1' => 'Ms.']; inside the User.php model and call for the array as $user->titles() whenever I need it. However this makes me twitch a bit because I'm calling the function on the model instance.
I really don't think creating a relation here is needed as there aren't more than ten possible titles.
Is there a better/right way to do these kind of things. I'm not that new to Laravel but I'm self thought so I'm trying to check my ways of doing stuff here.
I need to get into these oop basics a bit so any pointers here are helpfull.
Thanks in advance.
One way to do this is to keep titles in config file, like:
'titles' => [
1 => 'Mr.',
2 => 'Mrs.'
....
],
Benefits are:
you can edit this info at any time (keeping data hardcoded into model class is a bad practice)
you can use this list as is for select list building
you can keep data as TINYINT by keeping title IDs, sometimes it's a benefit
To build select list, do something like this:
{!! Form::select('titles', config('custom_config.titles'), 1) !!}
If you want dynamic select box write this code
$titles = User::lists('title', 'id'); // controller
{!! Form::select('title', $titles, null, ['class' => 'form-control']) !!} // view
You can use config files as #Alexey Mezenin suggested, but in this cases I prefer to use the database to store that kind of options (I mean in a different table, not in same users table), so I can add or delete options without touching the code and use the same form template/partial across different pages or projects if needed.
If you just need to retrieve the array it would be as easy as make a query in your controller and return it, but if you need it for form options I suggest you to use something like a form builder helper in which you get the form options from database and build your form using laravelcollective package, then you can pass the helper object to your view to show your fields and show whatever info you need.

Yii CActiveRecord multi level (nested) joins

I'm fairly new to Yii and so far I've managed to get by on my own but now I'm stuck.
I have a complex relational db (implemented in MySQL). I have the models for them and it's working properly my only problem is that I can't figure out how to make complex queries with CDbCriteria
The application is like an issue tracker so a user can report some problem and someone in charge of that type of problem will contact him/her.
Main tables relevant to the problem:
roles (multiple roles can be assigned to a user, e.g. a role can be 'accountant' or 'developer' )
issuetypes (a user with a role /e.g. accountant/ can create a new issue with a set of issue types /e.g. "printer problem"/
issues (every issue can only have one issue type)
A developer can create an issue like "new PHP version needed" but the accountant can't do that so I need to query the database for all the available issuetypes for a set of roles.
If the user have multiple roles (developer, tester) then I need the union of the issuetypes available for those roles.
So far it's working but when I need to take it a step further and query all the issues submitted with these issuetypes...I'm stuck.
Roughly I need to get the following query:
SELECT DISTINCT i.* FROM `issue` i
LEFT JOIN issuetype ON issuetype.id=i.issuetype_id
RIGHT JOIN role_has_issuetype rit ON rit.issuetype_id=issuetype.id
RIGHT JOIN role ON role.id=rit.role_id
WHERE role.role IN ('developer','tester') AND i.id IS NOT NULL
I know I could use the SQL query directly but the db backend will change in the future (most probably to Oracle) so I'd like to keep the abstraction as far as I could to avoid changing any hard-coded SQL statement and be "backend-independent".
The relevant parts of the models:
class Role extends CActiveRecord
{
public function relations()
{
return array(
'issuetypes' => array(self::MANY_MANY, 'Issuetype', 'role_has_issuetype(role_id, issuetype_id)'),
'users' => array(self::MANY_MANY, 'User', 'user_has_role(role_id, user_id)'),
);
}
}
class Issuetype extends CActiveRecord
{
public function relations()
{
return array(
...
'issues' => array(self::HAS_MANY, 'Issue', 'issuetype_id'),
'roles' => array(self::MANY_MANY, 'Role', 'role_has_issuetype(issuetype_id, role_id)'),
);
}
class Issue extends CActiveRecord
{
public function relations()
{
...
'issuetype' => array(self::BELONGS_TO, 'Issuetype', 'issuetype_id'),
);
}
}
I've tried something like this:
Issue::model()->with(
array(
'issuetype'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
'condition'=>'issuetype.roles IN ("developer","tester")',
)))->findAll();
It doens't work because issuetype has no column roles it's just a relation.
I've tried to do it in two steps. First get the issuetypes associated with the roles then get the issues.
The first part is working with this code:
$crit = new CDbCriteria();
$crit->addInCondition('roles.role',array('developer','tester'));
$crit->select = array('id');
$res=Issuetype::model()->with('roles')->findAll($crit);
But I don't know how to use the $res in another criteria.
(I'm not even sure this approach will work and even if will it's far from optimal)
I've read about a dozen SO answers and read the Yii forum together with the Yii docs but the examples I've found were not sufficient to solve this (at least I couldn't adopt those codes
to my problem)
I'm quite sure I'm just overlooking some obvious stuff but unfortunately I can't figure it out on my own.
Thanks
Sleeping on the problem helped :)
I've realized my mistake. I should have written this:
return Issue::model()->with(array(
'issuetype.roles'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
'condition'=>"roles.role IN ('developer','tester')",
)))->findAll();
I hope this will help someone in the future.

Yii - CGridView

I am writting my first application with Yii (v1.1.12), and the learning curve is a bit steep for me, so I need some help.
Imagine the following tables (with their relations):
detail (n:1) document
document (n:1) user
user (n:1) department
document (n:1) category
user is the table that holds the information about the users that can login and use the application.
I have managed to put together (using Gii and hacking about) a view that lists all the documents, and have also managed to display the category name instead of the category ID in the grid.
One of the features I want to implement is allow the user switch the view so (a) only the documents relating to the logged in user are listed, or (b) only the documents relating to his/her department.
I looked around a bit with no luck. Can anyone help?
Cheers,
George
UPDATE: Currenlty I display the list of documents using zii.widgets.grid.CGridView.
UPDATE 2:
Following Omar's reference to CDbCriteria I found this URL with a bit more detail on the subject.
I came up with the following model code, that works fine:
public function searchByUser($user_id)
{
$criteria=new CDbCriteria;
$criteria->condition = " user_id = ".$user_id;
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
public function searchByDepartment($user_id)
{
$criteria=new CDbCriteria;
$criteria->alias="p";
$criteria->join = "JOIN (SELECT u.id
FROM user u
INNER JOIN user uu
ON u.department_id = uu.department_id
WHERE uu.id = ".$user_id.") uu
ON p.user_id = uu.id";
return new CActiveDataProvider($this, array('criteria'=>$criteria,));
}
While the above works as expected, I was hoping for a solution that would not require me to write chopped SQL code at all. Not due to lazyness, but just to leverage more of the functionality of the framework.
I just have the feeling that this approach doesn't follow best practices (?).
Try to create your own CDBCriteria and define whatever conditions inside it and pass it as data provider to your grid view.
If you allowed the search inside the grid view, pass the criteria to the search function, and inside it, merge the passed criteria with criteria inside the search.
You could use relations to achieve what you're after. For example, to view all the documents and departments of a certain user you first need to set up the relations for that user, in your case you could set your users model up like so;
public function relations()
{
return array(
'documents' => array(self::HAS_MANY, 'Document', 'user_id'),
'department' => array(self::HAS_ONE, 'Department', 'department_id'),
);
}
You can then pull all the documents for the current user like so:
$user = User::model()->findByPk($userId);
$documents = $user->documents;
$documents will then be an array of active models for all that users documents.
To obtain all the documents of that users department, there's a couple of options. You could use relations again, adding to the Department model the following:
public function relations()
{
return array(
'users' => array(self::HAS_MANY, 'User', 'department_id'),
'documents' => array(self::HAS_MANY, 'Document', 'document_id', 'through'=>'users'),
);
}
Which should give you the ability to pull all of a departments documents like so;
$department = Department::model()->findByPk($departmentId);
$documents = $department->documents;
Which in turn would mean you could grab the users department documents like so:
$user = User::model()->findByPk($userId);
$documents = $user->department->documents;
There may well be a more efficient way to grab those by using a relation in the Users model, but it's too late for me to work that our right now ;)
Once you have an array of active record models, you can always pass them to a data provider by using CArrayDataProvider like so;
$dataprovider = new CArrayDataProvider($documents);
I've not tested any of those by the way, so they may need some editing!
You need to modify the search function on the appropriate model (I'm going to guess at documents). You'll already be able to see code in there you can use.
Add some parameters to the search function itself, which can be passed in from the controller. Then use these to determine which compare calls to make.

Categories