Laravel Eloquent hasMany Relation with Grouping and Sorting - laravel

I am using Laravel 5.6 and would like to get a collection of featured articles published in the last 2 weeks sorted by the number of views over the last week. Ideally using pagination on the model.
DB:
author (id, name, ....)
article (id, title, content, publish_date, ...)
article_view (id, article_id, date, views)
featured_article (id, article_id, created_at)
Models
class Author extends Model
{
protected $table = 'author';
public function articles()
{
return $this->hasMany(Article::class,'id','author_id');
}
}
class Article extends Model
{
protected $table = 'article';
public function author()
{
return $this->hasOne(Author::class,'id','author_id');
}
}
class FeaturedArticle extends Model
{
protected $table = 'featured_article';
static public function getFeaturedArticles($limit)
{
$articles = FeaturedArticles::where('created_at', '>=', Carbon::now()->subDays(14))->with(['article.author','article.articleViews'])->paginate($limit);
}
}
Then in the Controller or Feature or Job
$featured_articles = FeaturedArticle::getFeaturedArticles(15);
This works fine, but the results aren't sorted yet. How to sort the paginated results by the sum of article_view.views over 7 days. Is it possible?

Assuming you want the most viewed article first:
class FeaturedArticle extends Model
{
public function articleViews() {
return $this->hasMany(ArticleView::class, 'article_id', 'article_id');
}
}
$articles = FeaturedArticle::where('created_at', '>=', Carbon::today()->subDays(14))
->withCount(['articleViews' => function($query) {
$query->select(DB::raw('sum(views)'))
->where('date', '>=', Carbon::today()->subDays(7));
}])
->orderByDesc('article_views_count')
->with('article.author', 'article.articleViews')
->paginate($limit);
I replaced Carbon::now() with Carbon::today(). Otherwise, you wouldn't get articles that were published 14 days ago before the time of now().
Also, your relationships are incorrect:
public function articles()
{
return $this->hasMany(Article::class, 'author_id', 'id');
}
public function author()
{
return $this->belongsTo(Author::class, 'author_id', 'id');
}

Related

Laravel eloquent is very slow when joining two models

There is a model Invoice which equals to a purchase basket and model InvoiceItem which stores items inside a specific invoice. The model invoice has status field, if its value is 2999 and someone has verified it, it is successful. I need to get invoice items which are sold. Here is codes:
class Invoice extends Model
{
protected $fillable = [];
protected $table = 'payment_invoices';
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function scopeSuccessful($query)
{
return $query->where('status', 2999)->where('verified_at', '<>', null);
}
}
and
class InvoiceItem extends Model
{
protected $fillable = [];
protected $table = 'payment_invoice_items';
public function invoice()
{
return $this->belongsTo(Invoice::class, 'invoice_id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function vendor()
{
return $this->belongsTo(Vendor::class, 'vendor_id');
}
public function scopeNotDeleted($query)
{
return $query->where('deleted_at', null);
}
public function scopeSold($query)
{
return $query->notDeleted()->whereHas('invoice', function ($q) {
$q->successful();
});
}
}
The following statement
InvoiceItem::sold()->take(10)->get()
returns 10 sold items but it is very slow. it takes about 4 seconds to retrieve the information while the query
select *
from laravel.payment_invoice_items pii
join laravel.payment_invoices pi
on pi.id = pii.invoice_id
and pii.deleted_at isnull
and pi.status=2999
and pi.verified_at notnull
limit 10
takes about 800 milliseconds. Eloquent is very inefficient. Is there any solution to use the eloquent and get the same efficiency here?

How to get the supervisors that have approved all the submitted assessment

I am working on a project in Laravel-5.8
class Employee extends Model
{
protected $table = 'employees';
protected $primaryKey = 'id';
protected $fillable = [
'id',
'fist_name',
'last_name',
'supervisor_id',
];
public function assessment()
{
return $this->hasMany('App\Models\EmployeeAssessment', 'id', 'employee_id');
}
public function supervisor()
{
return $this->hasOne('App\Models\Employee', 'id', 'supervisor_id');
}
}
class Assessment extends Model
{
protected $table = 'employee_assessments';
protected $fillable = [
'id',
'employee_id',
'is_submitted',
'is_approved',
];
public function employee()
{
return $this->belongsTo('App\Models\Employee','employee_id');
}
}
Each Employee have a Supervisor, and Supervisor would have more than one Employee reporting to him.
The Employees are to submit their assessment which is to be approved by the supervisor. If the employee submits his assessment, then the is_submitted is 1, else 0.
The supervisor can only approve submitted (is_submitted = 1) assessment. When he approves is_approved is 1.
$employees_that_submitted_assessment = Assessment::where('is_submitted', 1)->get(); //Note:If the submitted assessment approved, is_approved = 1 else 0
$supervisor_completed_approvals = Employee::get();
How do I complete the query above to select the supervisors that have approved all the submitted assessments?
NOTE: All is:approved must be 1 for every where the is_submitted is 1 for the employees attached to the supervisor.
You could achieve this using query builder. I believe it would look something like this:
Employee::query()
->whereNull('supervisor_id')
->whereNotIn('supervisor_id', function($query){
$query->select('employee_id')
->from('employee_assignments')
->join('employee_assignments', 'employee.id', '=', 'employee_assignments.employee_id')
->where('employee_assignments.is_submitted', '=', 1)
->where('employee_assignments.is_approved', '=', 0);
})->get();
That makes use of a subquery. The subquery gets the employees with submitted but not approved assignments. The parent query then gets employees who are supervisors (assuming a null supervisor_id represents that) and who aren't supvervisors of employees in the list the subquery returned.
Try like this
$employees_that_submitted_assessment = Assessment::with(['employee' => function($e)
{
$e->with('supervisor');
}])
->where('is_submitted', 1)->get();
when you will dd($employees_that_submitted_assessment ); you will find supervisor
You should consider changing your is_approved column to approved_by if you need to track that information, since an employee may change supervisors in the future I would imagine.
In that case you can add the following relationships
Employee.php
public function supervised()
{
return $this->hasMany(Employee::class, 'superviser_id');
}
public function approvals()
{
return $this->hasMany(Assessment::class, 'approved_by');
}
public function supervisedAssessments()
{
return Assessment::whereHas('employee.supervisor' function ($query) {
$query->where('id', $this->id);
})->get();
}
public function pendingApprovals()
{
return Assessment::whereNull('approved_by')
->where('is_submitted', 1)
->whereHas('employee.supervisor' function ($query) {
$query->where('id', $this->id);
})->get();
}
public function approve(Assessment $assessment)
{
$assessment->approved_by = $this->id;
$assessment->save();
}
Assessment.php
public function approvedBy()
{
return $this->belongsTo(Employee::class, 'approved_by')
}
Now you can approve assessments with whatever logic you like, as below:
foreach ($supervisor->pendingAssessments() as $assessment) {
$supervisor->approve($assessment);
}
You have to proper design your Eloquent Model Relationship. As I can see, you did not provide the relationship between EmployeeAssessment and Supervisor objects. According to your assessment scenario, a supervisor can at least have one employee assessment and many occurrences of assessments must be handled by a supervisor. So we have one-to-many relationship between Supervisor and EmployeeAssessment.
You need to consider something like this:
Eloquent Model Relationship
class Supervisor extends Model{
public function employeeAssessment(){
return $this->hasMany('App\EmployeeAssessment');
}
}
class EmployeeAssessment extends Model{
public function supervisor(){
return $this->belongsTo('App\Supervisor');
}
}
Controller
$supervisors = Supervisor::with('employeeassements', 'employee')->orderBy('id', 'desc')->limit(25)->get(); //if you want to limit 25 assessments per query
View
#if(!empty($supervisors))
//traverse the assessment by supervisor with nested loops
#foreach($supervisors as $supervisor){
#foreach($supervisor->employeeassements as $assessment){
//some extra code here using $assessment object
//count the suppervisors here
({{ count(($supervisor->employeeassements) }})
}
}
#endif

Laravel Eloquent many to many relationship with translation

I have a problem with a many to many relationship and the translations of the terms.
I have 4 tables:
products
- id, price, whatever
products_lang
- id, product_id, lang, product_name
accessori
- id, active
accessori_lang
- id, accessori_id, lang, accessori_name
I'm trying to assign accessories to products with an intermediate table named:
accessori_products
this is the model for Product:
class Product extends Model {
protected $table = 'products';
public function productsLang () {
return $this->hasMany('App\ProductLng', 'products_id')->where('lang','=',App::getLocale());
}
public function productsLangAll() {
return $this->hasMany('App\ProductLng', 'products_id');
}
public function accessori() {
return $this->belongsToMany('App\Accessori', 'accessori_products');
}
}
this is the model for productLng:
class ProductLng extends Model {
protected $table = 'products_lng';
public function products() {
return $this->belongsTo('App\Product', 'products_id', 'id');
}
}
Then I have the model for Accessori:
class Accessori extends Model {
protected $table = 'accessori';
public function accessoriLang() {
return $this->hasMany('App\AccessoriLng')->where('lang','=',App::getLocale());
}
public function accessoriLangAll() {
return $this->hasMany('App\AccessoriLng');
}
public function accessoriProducts() {
return $this->belongsToMany('App\Products', 'accessori_products', 'accessori_id', 'products_id');
}
}
And the model for AccessoriLng:
class accessoriLng extends Model {
protected $table = 'accessori_lng';
public function accessori() {
return $this->belongsTo('App\Accessori', 'accessori_id', 'id');
}
}
the last model is for the relationship between the two tables above:
class ProductAccessori extends Model {
protected $table = 'accessori_products';
public function accessoriProducts() {
return $this->belongsTo('App\Product', 'accessori_id', 'products_id');
}
}
I'm trying to get the accessories of each product and to get also the translation but I'm having a lot of problem with this.
It's my first time with a many to many relation with translations too.
Can anyone put me on the right direction?
controller
$products = Product::has('accessori')->with([
'productsLang ',
'accessori' => function ($accessori){
$accessori->with([
'accessoriLang'
]);
}
])->get();
return $products;
you'll get products with accessori that has accessoriLang.

Laravel 5.3 belongsToMany return null, pivot data

I have a next models:
class Product extends Model{
protected $table = 'products';
public function payments()
{
return $this->belongsToMany('App\Payment', 'payment_product', 'product_id', 'payment_id')
}
}
class Payment extends Model{
protected $table = 'payments';
public function products(){
return $this->belongsToMany('App\Product');
}
}
Pivot table: payment_product(id, product_id, payment_id)
Eloquent
public function details($id){
$product = Product::with('payments')->find($id);
dd($product->payments); // return null
return view('products.details', ['product' => $product]);
}
I need to bring in a foreach, but $product->payments is null. Why is that ?
All the tables are not empty.
UPD
Sub query $this->belongsToMany('App\Payment', 'payment_product', 'product_id', 'payment_id');
Results:
try:
public function payments()
{
return $this->belongsToMany('App\Payment', 'payment_product', 'payment_id', 'product_id')
}
I think you have your FK in the wrong order.
In fact, I don't think you need to specify anything more than:
public function payments()
{
return $this->belongsToMany('App\Payment')
}
Looks like you are joining two tables based on unrelated fields.
When you do
return $this->belongsToMany('App\Payment', 'payment_product', 'product_id', 'payment_id')
your table products gets inner joined with payment_product on
products.product_id = payment_product.payment_id.
But I think these two columns are not the related keys. Instead, in your payment_product, join with the product_id in this table with products.product_id.
That might work.
Rename payments to payments_method:
public function payments_method(){
return $this->belongsToMany('App\Payment'); //or ('App\Payment', 'payment_product', 'payment_id', 'product_id')
}
$product = Product::find($id);
$product->payments_method; //this is work
This is magic:)

Laravel Eloquent hasMany and BelongsToMany not returning using with

I am trying to do a single query to get back an order and the card to charge, but getting an error.
Card model:
class Card extends Eloquent {
protected $guarded = array();
public static $rules = array();
public function user()
{
return $this->belongsTo('User');
}
public function orders()
{
return $this->hasMany('Order');
}
}
Order model:
class Order extends Eloquent {
protected $guarded = array();
public static $rules = array();
public function user()
{
return $this->belongsTo('User');
}
public function card()
{
return $this->hasOne('Card');
}
public function address()
{
return $this->belongsTo('Address');
}
public function orderItems()
{
return $this->hasMany('OrderItem');
}
}
What I am trying to get back:
$order = Order::with('card')->find($id);
This obviously doesn't work and I have tried several combos. I think the issue is with my models/relationships.
Any idea how I can get back the order with the card/token details?
DB info: Each order can have only one card_id and each card can be in many orders. There is no order_id in the card.
Orders table basically:
id | card_id
Cards table:
id | token
Trying to get the token col to return with the Order.
In your Order model, you need to change this:
public function card()
{
return $this->hasOne('Card');
}
to this:
public function card()
{
return $this->belongsTo('Card');
}
The reason is that you are defining the inverse of the hasMany relationship. With the belongsTo relationship, Eloquent will look for a card_id column on the orders table.

Resources