Attaching a count variable to an object - yii

I need some help in showing a count of posts of each user in a CGridView.
For example:
I have a User and Post model.
I can access the posts of a user through $user->posts
Is there something I can add to have $user->num_posts

You should simply use a stat relation, e.g. in your User model :
public function relations()
{
return array(
// ...
'posts' => array(self::HAS_MANY, 'Post', 'user_id'),
'num_posts' => array(self::STAT, 'Post', 'user_id'),
// ...
);
}
http://www.yiiframework.com/doc/guide/1.1/fr/database.arr#statistical-query
EDIT :
You should also use eager loading to build your dataprovider, e.g. :
$criteria=new CDbCriteria;
$criteria->with('num_posts');
About SQL queries, this should use only 3 queries :
one query for gridview page count,
one query to get users models,
one query to get users num_posts.
Just take a look at Yii logs to be sure.

You can use getter in your Post model.
Something like this:
public static function getpostscount($id)
{
$total=0;
$provider=Post::model()->findall('user_id=:user_id',array(':user_id'=>$id));
foreach ($provider as $data)
{
if ($data->userid==$id)//userid name of column with user id in post model
{$total++;
}
}
return $total;
}
Then in your view just pass user->id to getter and it will return you count of posts for this user. Something like this:
echo "User have".Post::model()->getpostscount($user->id);//$user->id is id of user for wich we will look count of posts
Regards.

Related

How to make a conditional active record relationship in Yii

I have Post, Comment, and User in a Yii api. When a Post is queried, the result should be the Post data, the User that made the Post, and any Comments for that Post, and the User who made the Comment with complete User data.
The Comment table includes a created_by field which is the user_id in the User table.
To get a single Post, here is the controller:
public function actionView($id){
$post = Post::find()
->innerJoinWith('user')
->joinWith('comments')
->where(['{{post}}.id' => $id])
->asArray()
->one();
return $post;
}
This returns a single Post by id, and any Comments.
To get all posts:
public function actionIndex(){
$posts = Post::find()
->joinWith('user', $eager)
->joinWith('comments', $eager)
->orderBy('updated_at DESC')
->limit(self::MAX_ROWS)
->asArray()
->all();
return $posts;
}
In the Post model, the Comment relationship is set like this:
public function getComments()
{
return $this
->hasMany(Comment::className(), ['object_id' => 'id']);
}
So this returns Comments if thre are any, but not the complete User data for each User who commented. So I added this to getComments()
->joinWith('user u2','u2.id = comment.created_by')
Which does return the User data along with the Comment, EXCEPT.... now actionIndex() only returns Posts that have Comments.
I reviewed this SO question but didn't find a solution. How do I conditionally include the joinWith only for Posts with Comments?
I would recommend you use ->with() instead of joinWith():
public function actionIndex() {
$posts = Post::find()
->with('user')
->with('comments')
->orderBy('updated_at DESC')
->limit(self::MAX_ROWS)
->asArray()
->all();
return $posts;
}
This way, you just use the relationship you already should have declared in your Post model class. After that, also add ->with() to your comments relationship declaration in your Post model class:
public function getComments() {
return $this
->hasMany(Comment::className(), [
'object_id' => 'id',
])
->with('user');
}
This way, you should get all the posts, the user and comments with their own users.

Yii2 REST API relational data return

I've set up Yii2 REST API with custom actions and everything is working just fine. However, what I'm trying to do is return some data from the API which would include database relations set by foreign keys. The relations are there and they are actually working correctly. Here's an example query in one of the controllers:
$result = \app\models\Person::find()->joinWith('fKCountry', true)
->where(..some condition..)->one();
Still in the controller, I can, for example, call something like this:
$result->fKCountry->name
and it would display the appropriate name as the relation is working. So far so good, but as soon as I return the result return $result; which is received from the API clients, the fkCountry is gone and I have no way to access the name mentioned above. The only thing that remains is the value of the foreign key that points to the country table.
I can provide more code and information but I think that's enough to describe the issue. How can I encode the information from the joined data in the return so that the API clients have access to it as well?
Set it up like this
public function actionYourAction() {
return new ActiveDataProvider([
'query' => Person::find()->with('fKCountry'), // and the where() part, etc.
]);
}
Make sure that in your Person model the extraFields function includes fKCountry. If you haven't implemented the extraFields function yet, add it:
public function extraFields() {
return ['fKCountry'];
}
and then when you call the url make sure you add the expand param to tell the action you want to include the fkCountry data. So something like:
/yourcontroller/your-action?expand=fKCountry
I managed to solve the above problem.
Using ActiveDataProvider, I have 3 changes in my code to make it work.
This goes to the controller:
Model::find()
->leftJoin('table_to_join', 'table1.id = table_to_join.table1_id')
->select('table1.*, table_to_join.field_name as field_alias');
In the model, I introduced a new property with the same name as the above alias:
public $field_alias;
Still in the model class, I modified the fields() method:
public function fields()
{
$fields = array_merge(parent::fields(), ['field_alias']);
return $fields;
}
This way my API provides me the data from the joined field.
use with for Eager loading
$result = \app\models\Person::find()->with('fKCountry')
->where(..some condition..)->all();
and then add the attribute 'fkCountry' to fields array
public function fields()
{
$fields= parent::fields();
$fields[]='fkCountry';
return $fields;
}
So $result now will return a json array of person, and each person will have attribute fkCountry:{...}

Yii Many to Many Relational Query

Using a many many relational query with users having many clients and clients having many users. Trying to view a record of a particular client for a particular user. And if that client is not associated with that user, redirect to a different page.
// the relation in the client model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'owners'=>array(self::MANY_MANY, 'User','owner_client(owner_id, client_id)'),
);
}
//the relation in the user model
public function relations()
{
return array(
'clients'=>array(self::MANY_MANY, 'Clients','owner_client(owner_id, client_id)'),
);
}
//determine if user can view this client
//client record
$client_record = Clients::model()->findByPk($id);
//many query to find users
$users = $client_record->owners;
//if user id is not found in array, redirect
if (!in_array(Yii::app()->user->id, $users))
{
$this->redirect(array('/site/dashboard'));
}
The above code redirects, even though I know the client is related to the user logged in
When you call $users = $client_record->owners;, what you're getting back is an array of all your user models that are associated with the current client. As a result, you're comparing integers to objects, which means your in_array() condition will always fail.
What I recommend is that you build a conditional query to do your verification check. Something like this should work:
$model = Clients::model()->with(
array(
'owners'=>array(
'select'=>'owner_id',
'condition'=>'user.id = '.Yii::app()->user->id,
),
)
)->findByPk($id);
if ($model === null) {
$this->redirect(array('/site/dashboard'));
}

How to map Eloquent with other tables?

This is the first time when i use ORM, so i wondering if it is possible to map it to other tables...
For example i have currently logged user. It is connected to the POSTS table through links table. For example if i want to select posts i do sql like this:
SELECT
`posts`.`id`', `posts`.`Name`, `posts`.`Description`
FROM
`links`,
`posts`
WHERE
`links`.`user_id` = 1 AND `links`.`post_id` = `posts`.`id`
How to extend Eloquent that if i request Posts::all() it would return posts only for current user...
You can define a query scope in your POST modal
public function scopeOfUser($query,$user_id)
{
return $query->join('links', 'links.post_id', '=', 'posts.id')
->where('links.user_id', '=', $user_id)
->select('posts.id', 'posts.Name', 'posts.Description');
}
Then use it like this:
$posts = POST::OfUser(1)->get();
Your post is a bit confusing but I hope my answer addresses it as you expect.
Warning:
Please refer to Laravel's (Many to Many) Relationship documentation for details.
To make things simple and comply with the Laravel convention, I strongly suggest you to use post_userinstead of link as the pivot table (post_user table should have user_id and post_id as columns).
You don't have to define a corresponding pivot model (that's the convention).
The following models are meant to map to respectfully the tables users and posts.
User Model:
in app/model/user.php (already there, just add the relationship definition)
class User extends Eloquent {
...
public function posts()
{
return $this->belongsToMany('Post');
}
...
}
Post Model:
in app/models/post.php (to be created of course).
...
class Post extends Eloquent {
public function users()
{
return $this->belongsToMany('User');
}
}
...
Retrieval of a user's posts:
Usually, you do the following in Laravel to get the current logged user:
$user = Auth::user(); // a User model / record
Assuming $user is of type User (Model), you can retrieve all its posts using:
$user->posts(); // a Collection

How do I make a construct to have beforeAuth only apply to certain views/functions in Laravel 4

I have a resource in Laravel I have called artists with an ArtistsController. I would like to add filters to some of the pages, but not all. I know I can add a filter to all of the functions/views in the resource controller like so:
public function __construct()
{
$this->beforeFilter('auth', array('except' => array()));
}
How do I add the beforeAuth filter to only a certain view/function? I would like a user to be logged in in order to go the "index" view, but I would like a user to be able to go to the "show" pages without necessarily being logged in:
public function index()
{
$artists = Artist::all();
return View::make('artists.index', compact('artists'))
->with('artists', Artist::all())
->with('artists_new', Artist::artists_new());
}
public function show($id)
{
$artist = Artist::find($id);
return View::make('artists.show', compact('artist'))
->with('fans', Fan::all());
}
Is there a way to do this? Thank you.
Not sure if this helps but you could use the only key instead of the except (if I understand your question correctly).
$this->beforeFilter('auth', array('only' => array('login', 'foo', 'bar')));
Although that would still go in the constructor.