PHP/Laravel get rows with certain column values - php

As can be seen in the linked image, I want to query the records that have the same chapter number (skipping the zeros). Let's say I have 50 chapters, so the query will yield 50 sets, each set corresponding to certain column value i.e. chapter number.
How can I limit the query to that in my Laravel controller?

Get chapter groups, Like drawing in the image
$chapters = Translation::where('chapter', '!=', 0)->get()->groupBy('chapter');

Without pagination:
$chapters = Translation::where('chapter', '!=', 0)->get()->groupBy('chapter');
With pagination :
$posts = Translation::where('chapter', '!=', 0)->orderBy('chapter', 'ASC')->paginate($request->get('per_page', 2));
$grouped_by_chapter = $posts->mapToGroups(function ($post) {
return [$post->chapter => $post];
});
$posts_by_chapter = $posts->setCollection($grouped_by_chapter);
for more: https://laravel.com/docs/8.x/collections#method-groupby

Without knowing more about your project setup, I'd say:
$chapters = DB::table('translations')
->select('chapter')
->distinct()
->where('chapter', '<>', 0)
->get();
If you use eloquent models:
$chapters = Translations::get()
->pluck('chapter')
->flatten()
->filter()
->values()
->all();

In the Elequent model, you can create a hasMany relation to 'translations',
class Chapter extends Model
{
public function translations()
{
return $this->hasMany(Translation::class, 'chapter');
}
and then retrieve the 'Chapter' models with translations.
$chapters = Chapter::with('translations')->get()

A lot of these answers are correct, but I thought I'd offer a different approach.
1. Create a new table
Consider creating an additional table called chapters.
Do this by using the command:
php artisan make:model Chapter -m
This'll create a model and migration.
The migration will look like this:
Schema::create('chapters', function (Blueprint $table) {
$table->id();
$table->integer('number')->unsigned();
$table->string('heading');
$table->timestamps();
});
2. Add the foreign key to your old table
Then modify your model from your screenshot. From here on, I'll assume the same as everyone else, that this table is called translation, with a model called Transaltion.
Your new migration should look like this:
Schema::create('translations', function (Blueprint $table) {
$table->id();
$table->foreignId('chapters_id')->constrained()->onUpdate('cascade')->onDelete('cascade');
$table->string('translation');
$table->timestamps();
});
3. Add the relationship to the models
Translation model
public function chapters()
{
return $this->belongsTo(Chapter::class);
}
Chapter model
public function translations()
{
return $this->hasMany(Translation::class);
}
4. Using the new relationship
Instead of having to use groupBy, or any of the other methods, you now can query on each heading.
An example of some of those below.
4.1 How many chapters do we have?
Chapter::count();
4.2 How many sentences are there in Chapter 1?
Chapter::where('number', 1)->translations()->count();
// or if you want to get a Collection of them
Chapter::where('number', 1)->translations;
// or if you just want a certain few columns
Chapter::where('number', 1)->translations()->select('translation', 'sentence')->get();
4.3 How do I get all chapters and corresponding translations?
$chapters = Chapter::with('translations')->get();
Then in your blade view, do:
#foreach ($chapters as $chapter)
<h1>{{ $chapter->heading }}</h1>
<p>Sentences:</p>
#foreach ($chapter->translations as $sentence)
<p>{{ $sentence }}</p>
#endforeach
#endforeach

Assuming you have a Translation model and want to use Eloquent:
$chapters = Translation::where('chapter', '!=', 0)->get()->groupBy('chapter');
The above says get all Translation where the Chapter they are associated to is not Chapter zero and group all translations by the chapter column. So if you have 50 chapters you'll have 50 collections each with their translations contained within.
If you just want specific columns, you can use select() and provide it with only the columns you want.

Related

How to check relation data of a collection before sending it to view

I have a Controller method like this:
public function awaiting()
{
$producers = Producer::where('producer_process',4)->get();
$producers_list = [];
foreach($producers as $producer){
if($producer->brand->brand_rejected == 0){
array_push($producers_list, $producer);
}
}
return view('admin.brands.awaiting', compact('producers_list'));
}
So basically there's One To One relationship between Producer model & Brand model.
In order to get the collection of brands table records that has producer_process of 4 and ALSO the brand_rejected field of related brands table record must be set to 0, I added an array_push and check the condition.
Now this works fine and properly shows the correct data but I wanted to know, what is the shorthand method of doing this with Eloquent relationships?
I mean is there any concise and useful method written in Eloquent relationships that can do this without using array_push or another foreach loop?
You can use whereHas to constrain the result set based on the existence of a relationship. Here we are saying we only want producers that have the field 'produce_process' set to 4 and have a brand with a field of 'brand_rejected' set to 0:
$producers = Producer::where('producer_process', 4)
->whereHas('brand', function ($q) { $q->where('brand_rejected', 0); })
->get();
If you want these producers to have their brand relationship loaded to use you should eager load that. Before the get call you can tell it to load the relationship:
$producers = Producer::where(...)->whereHas(...)->with('brand')->get();
Laravel 5.8 Docs - Eloquent - Relationships - Querying Relationship Existence whereHas
Laravel 5.8 Docs - Eloquent - Relationships - Eager Loading with
You can try this:
public function awaiting()
{
$producers = Producer::where('producer_process',4)
->with('brand', function($q) {
$q->where('brand_rejected', 0);
})->get();
// dd($producers);
dd($producers->pluck(brand));
Sure you can use the method with() also with the where() clause to can apply some conditions to the relationship
Example
$yourQuery->with('brand', function($query){
$query->where('brand_rejected', 0);
});
check this for more info
https://laravel.com/docs/9.x/eloquent-relationships#constraining-eager-loads
I hope it's helpful

Dynamic Property call giving error (Property [game] does not exist on the Eloquent builder instance.)

I have two models: Game and Game_Assignment. Game_Assignment tells whose job it is to play a game.
I am trying to count the number of Game_Assignment's that a user has their id on that also have a specific value on the Game model that it relates to. I'll just get into the Models/the code
Game Model Relationship:
public function assignments() {
return $this->hasMany('App\Models\Game_Assignment', 'game_id');
}
Game_Assignment Relationship:
public function game() {
return $this->belongsTo('App\Models\Game', 'game_id');
}
Where things are going wrong (in a queue job, if that makes a difference)
$gamesDue = Game_Assignment::where('statistician_id', $statistician->id)->game->where('stats_done', '!=', 'yes')->count();
I have also tried the following two things, neither worked:
$gamesDue = Game_Assignment::where('statistician_id', $statistician->id)->game()->where('stats_done', '!=', 'yes')->count();
and...
$gamesDue = Game_Assignment::where('statistician_id', $defaultStatistician->id)->with(['games' => function($query) {
$query->where('stats_done', '!=', 'yes');
}])->count();
None of these work, and the first one I showed threw an error:
Property [game] does not exist on the Eloquent builder instance.
Anyone have an idea of where I am going wrong? I am using this link as my reference https://laravel.com/docs/5.8/eloquent-relationships#eager-loading
When using the query builder of your Game_Assignment model, you cannot simply switch context to the query builder of Game. You can only call ->game() or ->game after you retrieved one or many model instances of Game_Assignment with first() or get().
So, in your particular case, you were looking for whereHas('game', $callback) (where $callback is a function that applies constraints on the foreign table) in order to add a constraint on the foreign table:
use Illuminate\Database\Eloquent\Builder;
$gamesDue = Game_Assignment::query()
->where('statistician_id', $statistician->id)
->whereHas('game', function (Builder $query) {
$query->where('stats_done', '!=', 'yes');
})
->count();
Side note: a column (stats_done) that seems to hold a boolean value (yes/no) should be of boolean type and not string/varchar.

Laravel Migration - Create a new column filled from existing column

I'm trying to create a migration that makes a new column and fills it with data from existing column.
I want to turn the name column into a slug (using the helper function) and save that in a slug column.
I've tried this but no luck:
public function up()
{
Schema::table('teams', function(Blueprint $table)
{
//
$table->string('slug', 100);
});
$teams = DB::table('teams')->get();
foreach ($teams as $team)
{
$team->slug = str_slug($team->name, "-");
$team->save();
}
}
Am i being an idiot? Can i make this work?
Thanks
Assuming you have a Team model:
$teams = App\Team::all();
foreach($teams as $team) {
$team->slug = str_slug($team->name, "-");
$team->save();
}
You're trying to use Eloquent ORM syntax ($team->save) in a code that is actually using Query Builder. You'd better choose one or the other (ORM or Query building). My version uses Eloquent ORM. Of course you could have used Query Builder syntax all the way, like this:
$teams = DB::table('teams');
foreach($teams as $team) {
DB::table('teams')
->where('id', $team->id)
->update(['slug' => str_slug($team->name)]);
}
And basically a Query Builder select command (like $teams = DB::table('teams');) will return an array of stdClass objects (which have not a "save" method) , whereas Eloquent ORM select will return a collection of objects of the specified model, which do have the "save" method.
You are not using the name column, but the (empty) slug. Try this instead:
$team->slug = str_slug($team->name, "-");

Select from two tables Laravel 5

I use Laravels many to many relations. I have 2 tables projects and groups and pivot table project_group
Now I can do something like this:
$groups = \App\Project::findOrFail(Auth::user() -> id)->groups()->where('adminid','=',Auth::user()->id)->get();
It will return just Groups...Like this:
Design
SEO
But I need to return like this:
Design(Project2,Project3)
SEO(Porject1)
So for each loop I need to get group and all project linked to that group.
Here is my relation into Project modul:
public function groups(){
return $this ->belongsToMany('App\Group','project_group')->withPivot('admin_id');
}
You can define the inverse of the relationship in your Group model.
public function projects(){
return $this->belongsToMany('App\Project','project_group')->withPivot('admin_id');
}
And then, use it in your eloquent query.
$groups = Group::with('projects')->get();
You'll be able to loop through your groups and in each group get all projects.
foreach($groups as $group) {
foreach($group->projects as $project){
//your project
}
}
I don't really understand the usage of Auth::user() -> id but if you only want project where the current user is admin you can use a condition in the with function.
$groups = Group::with(['projects' => function($query) {
$query->where('adminid', Auth::user()->id)
}])->get();

How to order by pivot table data in Laravel's Eloquent ORM

In my Database, I have:
tops Table
posts Table
tops_has_posts Table.
When I retrieve a top on my tops table I also retrieve the posts in relation with the top.
But what if I want to retrieve these posts in a certain order ?
So I add a range field in my pivot table tops_has_posts and I my trying to order by the result using Eloquent but it doesn't work.
I try this :
$top->articles()->whereHas('articles', function($q) {
$q->orderBy('range', 'ASC');
})->get()->toArray();
And this :
$top->articles()->orderBy('range', 'ASC')->get()->toArray();
Both were desperate attempts.
Thank you in advance.
There are 2 ways - one with specifying the table.field, other using Eloquent alias pivot_field if you use withPivot('field'):
// if you use withPivot
public function articles()
{
return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range');
}
// then: (with not whereHas)
$top = Top::with(['articles' => function ($q) {
$q->orderBy('pivot_range', 'asc');
}])->first(); // or get() or whatever
This will work, because Eloquent aliases all fields provided in withPivot as pivot_field_name.
Now, generic solution:
$top = Top::with(['articles' => function ($q) {
$q->orderBy('tops_has_posts.range', 'asc');
}])->first(); // or get() or whatever
// or:
$top = Top::first();
$articles = $top->articles()->orderBy('tops_has_posts.range', 'asc')->get();
This will order the related query.
Note: Don't make your life hard with naming things this way. posts are not necessarily articles, I would use either one or the other name, unless there is really need for this.
For Laravel 8.17.2+ you can use ::orderByPivot().
https://github.com/laravel/framework/releases/tag/v8.17.2
In Laravel 5.6+ (not sure about older versions) it's convenient to use this:
public function articles()
{
return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range')->orderBy('tops_has_posts.range');
}
In this case, whenever you will call articles, they will be sorted automaticaly by range property.
In Laravel 5.4 I have the following relation that works fine in Set model which belongsToMany of Job model:
public function jobs()
{
return $this->belongsToMany(Job::class, 'eqtype_jobs')
->withPivot(['created_at','updated_at','id'])
->orderBy('pivot_created_at','desc');
}
The above relation returns all jobs that the specified Set has been joined ordered by the pivot table's (eqtype_jobs) field created_at DESC.
The SQL printout of $set->jobs()->paginate(20) Looks like the following:
select
`jobs`.*, `eqtype_jobs`.`set_id` as `pivot_set_id`,
`eqtype_jobs`.`job_id` as `pivot_job_id`,
`eqtype_jobs`.`created_at` as `pivot_created_at`,
`eqtype_jobs`.`updated_at` as `pivot_updated_at`,
`eqtype_jobs`.`id` as `pivot_id`
from `jobs`
inner join `eqtype_jobs` on `jobs`.`id` = `eqtype_jobs`.`job_id`
where `eqtype_jobs`.`set_id` = 56
order by `pivot_created_at` desc
limit 20
offset 0
in your blade try this:
$top->articles()->orderBy('pivot_range','asc')->get();
If you print out the SQL query of belongsToMany relationship, you will find that the column names of pivot tables are using the pivot_ prefix as a new alias.
For example, created_at, updated_at in pivot table have got pivot_created_at, pivot_updated_at aliases. So the orderBy method should use these aliases instead.
Here is an example of how you can do that.
class User {
...
public function posts(): BelongsToMany {
return $this->belongsToMany(
Post::class,
'post_user',
'user_id',
'post_id')
->withTimestamps()
->latest('pivot_created_at');
}
...
}
You can use orderBy instead of using latest method if you prefer. In the above example, post_user is pivot table, and you can see that the column name for ordering is now pivot_created_at or pivot_updated_at.
you can use this:
public function keywords() {
return $this->morphToMany(\App\Models\Keyword::class, "keywordable")->withPivot('order');
}
public function getKeywordOrderAttribute() {
return $this->keywords()->first()->pivot->order;
}
and append keyword attribiute to model after geting and use sortby
$courses->get()->append('keyword_order')->sortBy('keyword_order');

Categories