Laravel Migration - Create a new column filled from existing column - php

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, "-");

Related

PHP/Laravel get rows with certain column values

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.

Laravel: Count number of rows in a relationship

I have the following relationship:
A venue has many offers
A offer has many orders
I have the following Eloquent model to represent this:
class Venue {
public function orders()
{
return $this->hasManyThrough(Order::class, Offer::class);
}
}
I want to determine the total number of orders for venues with location_id = 5 using Laravel's Eloquent model.
The only way I managed to do this is as follows:
$venues = Venue::where('location_id', 5)->with('orders')->get();
$numberOfOrders = 0;
foreach($venues as $venue) {
$numberOfOrders += $venue->orders->count();
}
dump($numberOfOrders); // Output a single number (e.g. 512)
However, this is obviously not very efficient as I am calculating the count using PHP instead of SQL.
How can I do this using Eloquent model alone.
You can use Eloquent. As of Laravel 5.3 there is withCount().
In your case you will have
$venues = Venue::where('location_id', 5)->with('orders')->withCount('orders')->get();
Then access it this way
foreach ($venues as $venue) {
echo $venue->orders_count;
}
Can find reference here: https://laravel.com/docs/5.3/eloquent-relationships#querying-relations
$venues = Venue::with([
'orders' => function ($q) {
$q->withCount('orders');
}
])->get();
then use it this way for getting single record
$venues->first()->orders->orders_count();
Alternatively, you can use this way too for collections
foreach($venues as $venue)
{
echo $venue->order_count;
}
If you are using Laravel 5.3 or above you can use withCount.
If you want to count the number of results from a relationship without
actually loading them you may use the withCount method, which will
place a {relation}_count column on your resulting models. For example:
$venues = Venue::withCount(['orders'])->get;
foreach ($venues as $venue) {
echo $venue->orders_count;
}
You can read more about withCount in the Laravel Documentation.
If you are using lower than 5.3, you can make a custom relation on your Venue model:
public function ordersCount()
{
return $this->belongsToMany('App\Models\Order')
->selectRaw('venue_id, count(*) as aggregate_orders')
->groupBy('venue_id');
}
public function getOrderCount()
{
// if relation is not loaded already, let's do it first
if (!array_key_exists('ordersCount', $this->relations)) {
$this->load('ordersCount');
}
$related = $this->getRelation('ordersCount')->first();
// then return the count directly
return ($related) ? (int) $related->aggregate_orders : 0;
}
which can then be used as: Venue::with('ordersCount');. The benefit of this custom relation is you only are querying the count rather than the querying all of those relations when they are not necessary.

Building Laravel query adding where statement by relationship's field

I am not able to construct simple Laravel query.
I have translation with categories (translation.category_id is foreign key to category.id). Moroever category also has property is_technical.
What I need is:
- get all translations where translation's category.is_technical = 1.
Currently I am haveing this query:
$match = ['lang1_code' => $langfrom, 'lang2_code' => $langto];
$translation = Translation::where($match)->orderByRaw("RAND()")->take(4)->get();
But this query doesn't join category (I have relationship in my db and also in my models). Thus how to join category and set where is_Technical = 1?
I believe this is basic question, but I am new to Laravel and I cannot find answer in documentation.
you need whereHas. See Laravel Document for more info
http://laravel.com/docs/5.1/eloquent-relationships
Here is an example, correct your model name.
//Translation.php
public function category() {
return $this->belongsTo('Category', 'category_id');
}
// query
$translation = Translation::whereHas('category', function($q) {
$q->where('is_technical', '=', 1);
})->where($match)->orderByRaw("RAND()")->take(4)->get();

Laravel : Filtering Eloquent Collections by unique column value after retrieval

From the laravel docs we know that we can filter Eloquent collections after retrieval from database by applying a filter :
$users = $users->filter(function($user)
{
return $user->isAdmin();
});
Does anyone knows if we can filter the collections by a unique column/attribute that is not the primary key?
Edited: Say I have an column slug and I want to filter the collection based on distinct slug values , removing duplicates along the way.
I know this is old but you could try something like this:
$existingSlugs = [];
$users = $users->filter(function($user) use (&$existingSlugs)
{
$duplicate = in_array($user->slug, $existingSlugs);
if(!$duplicate) {
$existingSlugs[] = $user->slug;
}
return !$duplicate;
});

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