Retrieving data in Laravel - php

I can't find solution how to retrieve links that belongs to user where campaign is active?
Here are my models:
User model:
public function links(){
return $this->hasMany('Link','user_id');
}
public function analytic(){
return $this->hasManyThrough('Analytic','Link');
}
Campaign model:
public function task(){
return $this->hasMany('Task','campaign_id');
}
public function links(){
return $this->hasManyThrough('Link','Task');
}
Task model:
public function campaign(){
return $this->belongsTo('Campaign','campaign_id');
}
public function links(){
return $this->hasMany('Link','task_id');
}
public function analytic(){
return $this->hasManyThrough('Analytic','Link');
}
Link model:
public function task(){
return $this->belongsTo('Task','task_id');
}
public function user(){
return $this->belongsTo('User','user_id');
}
public function analytic(){
return $this->hasMany('Analytic','link_id');
}
So, admin creates a campaign and task that belongs to campaign and users can start task, once they start task they are getting their link assigned for that task and campaign.
In this case I can easily count all user links:
$user = User::find(1);
echo $user->links->count();
But my question is how to echo only links that are related to active campaigns.
Again: CAMPAIGN has many TASK, TASK has many LINKS, USER has many LINKS.
Get the number of links that belongs to user only for active campaigns.
Similar thing with analytics :(
Every link has many analytics ( tracking clicks )
Analytic model:
public function links(){
return $this->belongsTo('Link','link_id');
}
Also need to echo the count of links only for tasks that are under active campaign.

First off, you don't need to run query for a user if you have his id:
$user=User::find($id); // redundant
// instead
Link::where('user_id', $id)->count();
Now to get those links for active campaigns:
Link::whereHas('task', function ($q) {
$q->whereHas('campaign', function ($q) {
$q->active(); // or where('status', 'active'); see below
});
})->where('user_id', $id)->get();
// of course you can start with $user->links()->whereHas ... instead
My example covers scope for a campaign:
// Campaign model
public function scopeActive($query)
{
$query->where('status', 'active');
}
The same goes for count:
Link::whereHas('task', function ($q) {
$q->whereHas('campaign', function ($q) {
$q->active();
});
})->where('user_id', $id)->count();
You can wrap that whereHas chain in a scope as well:
// Link model
public function scopeOnlyActiveCampaigns($query)
{
$query->whereHas('task', function ($q) {
$q->whereHas('campaign', function ($q) {
$->active();
});
});
}
// then simply:
Link::onlyActiveCampaigns()->where('user_id', $id)->count();
And as stated in the comment: use consistent names for relations, it will make your life easier:
$link->analytic; // collection, better analytics
$analytic->links; // single Link model, better link etc

Related

Laravel 5.7 not returning new record when global scope applied

I'm sure I'm missing something simple here but I am completely at a loss so any input would be greatly appreciated.
I have two models, User and Account with a many to many relationship with the model Channel. Accounts can be associated with multiple channels and users can also be associated with multiple channels. This has been created so that users can only access accounts that are associated with channels they are also associated with.
In order to do the filtering, I have applied a global scope to the account model so when I perform a query, it only returns accounts associated with the channels that the user is associated with. This works as intended for everything except newly created accounts.
If I call $account = Account::find($id) on a newly created account it returns null. If I drop the global scope it returns the account.
The only way I have found to fix the problem is if I sync the pivot table for the channel_user table and only include a single channel that is also associated with the account.
It feels like something is being cached somewhere but I'm not sure where to go from here. Please let me know what else you need to know
Account Model:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new ChannelScope);
}
public function channels()
{
return $this->belongsToMany('App\Channel');
}
public function user()
{
return $this->belongsTo('App\User');
}
User Model:
public function accounts() {
return $this->hasMany('App\Account');
}
public function channels(){
return $this->belongsToMany( 'App\Channel' );
}
Channel Model:
public function accounts()
{
return $this->belongsToMany('App\Account');
}
public function users(){
return $this->belongsToMany('App\User');
}
Channel Scope:
public function apply(Builder $builder, Model $model)
{
$channels_ob = Auth::user()->channels;
$channels = array();
foreach ($channels_ob as $channel){
array_push($channels,$channel->id);
}
$builder->whereHas('channels', function ($q) use ($channels){
$q->where('channels.id','=', $channels);});
}
AccountController.php Store:
$account->save();
if (isset($request->chk_channels)){
foreach($request->chk_channels as $channel){
$ch = Channel::where('name',$channel)->get();
$ch_array[] = $ch[0]->id;
}
}
$account->channels()->sync($ch_array);
UserController.php update_channels:
public function update_channels(Request $request, $id)
{
$user = User::find($id);
if ($user->hasPermission('view_all_accounts')){
if (isset($request->chk_channels)){
foreach($request->chk_channels as $channel){
$ch = Channel::where('name',$channel)->get();
$ch_array[] = $ch[0]->id;
}
$user->channels()->sync($ch_array);
}
}
You can't have a column value equivalent to an array. You're building up an array of channels in your scope and then checking equivalency:
$q->where('channels.id','=', $channels);
Perhaps, you want whereIn:
$q->whereIn('channels.id', $channels);

How to get third relation in laravel

I have the following models: User, Device, Product.
User
public function devices()
{
return $this->hasMany('App\Device');
}
Device
public function user()
{
return $this->BelongsTo('App\User');
}
public function reading()
{
return $this->hasMany('App\Reading', 'device_id', 'part_id');
}
public function product()
{
return $this->hasOne('App\Product');
}
Product
public function device()
{
return $this->belongsTo('App\Device');
}
The following query pulls my users and all their devices, but inside that collection is not the relation from device to product.
$deviceSingles = User::with('devices')->get();
This query gets me all the products with all devices assigned to it
$deviceSinglesTwo = Product::with('device')->get();
How do I get that third relation, attached to my initial query so i can do this
$deviceSingles->device->product->title
Use nested eager loading.
To eager load nested relationships, you may use "dot" syntax.
User::with('devices.product')->get()

Laravel 5.2 - Selecting only articles that has comments

I built a commenting system, and I'm working on a page that shows all the comments that are waiting for approval.
The relationship:
Article.php
public function comments()
{
return $this->hasMany('App\ArticleComment');
}
ArticleComment.php
public function article()
{
return $this->belongsTo('App\Article');
}
Now, I want to select only the articles that have comments that are waiting for approval (status column on article_comments table equals 0).
Any easy way of doing it? (Of course I can get all articles and check on each one if it has comments)
The other answer will work but you asked for an easy (also re-usable) approach to use so I would suggest to create a scope method in your ArticleComment model using something like the following:
In your Article model:
use App\ArticleComment;
use Illuminate\Database\Eloquent\Model;
class Article extends Model {
// Relation for comments
public function comments()
{
return $this->hasMany(ArticleComment::class);
}
// Relation for pending comments
public function pendingComments()
{
return $this->comments()->pending();
}
}
In your ArticleComment model:
// Query scope for pending comments
public function scopePending($query)
{
$query->whereStatus(0);
}
So, you can use something like this:
$posts = Post::has('pendingComments')->get();
Also, you may chain with like:
$posts = Post::has('pendingComments')->with('pendingComments')->get();
$articles = Article::whereHas('comments', function($query) {
$query->where('status', 0);
})->get();

Laravel - Relationships issue

I have 3 models:
class Site extends Model
{
public function users()
{
return $this->belongsToMany('App\Models\User');
}
public function stats()
{
return $this->hasMany('App\Models\Stat');
}
}
class User extends Model
{
public function sites()
{
return $this->belongsToMany('App\Models\Site');
}
public function stats()
{
return $this->belongsToMany('App\Models\Stat');
}
}
class Stat extends Model
{
public function users()
{
return $this->belongsToMany('App\Models\User');
}
public function sites()
{
return $this->belongsTo('App\Models\Site');
}
}
So there are :
a many to many relation between sites and users
a many to many relation between users and stats
a one to many relation between site and stats
A site have a list of stats and from this list, an user can have some stats.
I'm trying to get all sites and foreach site, count of stats for the connected user.
For the moment i tried :
//repository
function getAll($user_id = 0)
{
$with = [];
$with['users'] = function ($query) use ($user_id) {
$query->where('id', '=', $user_id);
};
return Site::with($with)->orderBy('name')->get();
}
//controller
$sites = getAll($user_id);
//view
foreach($sites as $site){
$count_stats = $site->users->first()->stats->where('site_id',$site->id)->count();
}
It works but it is not very elegant, it does a lot of sql requests and the page is slower.
Do you have a better solution ?
If Sites have many Users, and Sites have many Stats, then a User has many Stats through Site
Modify User class:
public function stats() {
return $this->hasManyThrough('App\Stat', 'App\Site', 'user_id', 'site_id');
}
Eager load:
$user = User::with('stats')->find($user_id);
$stats = $user->stats();
Also, I think your Stat should belongsTo Site, since Site hasMany Stat. You need to change a lot of the belongsToMany as well since they look incorrectly used.

Laravel models to return null relation?

I am writing a website for photo posts and I have these functions relating likes (they determine if the user is liking the specific post or not)
Post Model:
public function likes()
{
return $this->hasMany('Like');
}
public function isLiked()
{
return $this->likes()->where('user_id', Auth::user()->id);
}
Post Controller function for example:
public function postsByType($type)
{
if($this->user){
$posts = Post::with('isLiked')->where('type', '=', $type)->paginate(12);
} else {
$posts = Post::where('type', '=', $type)->paginate(12);
}
return $posts;
}
Is there any way to return null in MODEL function when user is not logged in, without running a query?
I want to avoid writing that if in post controller
I thought about the following solution but it's not working...
public function isFollowing()
{
return $this->setRelation('isFollowing', null);
}
getting this error:
Call to undefined method Illuminate\Database\Query \Builder::addEagerConstraints()
Since you probably always want to fetch the relation (except if there's no user logged in) I suggest you do something like this in your model:
(I also renamed the relationship to liked, you'll see later why)
public function newQuery(){
$query = parent::newQuery();
if(Auth::check()){
$query->with('liked');
}
return $query;
}
Now every time a query is run with the model with('isLiked') will be added if the user is logged in.
One problem remains though. If you access isLiked the query will be run anyways. And even for every post because it's not eager loaded. You can fix that by adding an attribute accessor:
public function getIsLikedAttribute(){
if(Auth::guest) return false;
return ! $this->liked->isEmpty();
}
So in your view you can just do this:
#if($post->isLiked)
Note: It would be nicer to move the things inside newQuery() to a global scope. Make sure to check out how to do that in the documentation if you're interested.
Here's an example with a scope. Create a class, let's call it LikedScope:
class LikedScope implements Illuminate\Database\Eloquent\ScopeInterface {
public function apply(Builder $builder, Model $model){
if(Auth::check()){
$builder->with('liked');
}
}
public function remove(Builder $builder, Model $model){
}
}
And then add it to your model:
public static function boot(){
parent::boot();
static::addGlobalScope(new LikedScope);
}

Categories