laravel 4 group collection/results - laravel

I am trying to do a simple group by 'alpha' in my loop for a glossary page but not sure how to achieve this. My data is
alpha glossary_title
A Apple B Banana A Another Apple!
GlossaryController.php
public function index() {
$glossary = Glossary::groupBy('alpha')->orderBy('glossary_title', 'asc')->get();
return View::make('glossary')->with('glossary', $glossary);
}
migration
Schema::create('glossary', function(Blueprint $table)
{
// columns
$table->increments('id');
$table->string('glossary_title');
$table->text('glossary_desc')->nullable();
$table->char('alpha', 1);
$table->timestamps();
});
glossary.blade.php
#foreach($glossary as $glossary)
<li>{{ $glossary->glossary_title}} <br /> {{ $glossary->glossary_desc}}</li>
#endforeach

This will return a new collection groupped by $glossary->alpha:
$glossary = Glossary::orderBy('glossary_title', 'asc')->get();
$glossary = $glossary->groupBy('alpha');
// same as this:
$glossary = $glossary->groupBy(function ($entry) {
return $entry->alpha;
});
You will need 2 loops in your view then, first for groups, second for entries in each group.

Related

how to retrieve limited number of related model and sort collection by related model in laravel?

I have 3 model
Shop model, Product model, Picture model
I want to retrieve a collection of shops with last 3 Product model with their pictures and sort my collection based on newest product.
I tried leftjoint and joint in laravel 6 to be able to sort the result but i get all shops`product (i only need last 3 product for each shop),
when I use joint I cant retrieve product pictures
I also have tried “with” method in laravel , I couldnt sort the result based on product.creatred_at and also i get all related product in this method too.(as i mentioned i need the last 3 product)
class Shop extends Model
{
public function products()
{
return $this->hasMany('App\Product');
}
}
class Product extends Model
{
public function shop()
{
return $this->belongsTo('App\Shop');
}
public function pictures()
{
return $this->morphMany('App\hPicture', 'pictureable');
}
}
Shop::select('shops.*', 'products.id', 'products.shop_id', 'products.name as pname', 'products.user_id', 'products.code', 'products.price')
->with(['pictures', 'products.pictures'])
->leftjoin('products', function ($leftJoin) {
$leftJoin->on('shops.id', '=', 'products.shop_id');
});
$dataList = $dataList->orderBy($field, $order);
$dataList = $dataList->paginate(5)->appends(['sortField' => $field, 'sortOrder' => $order]);
the table layout for product and shop model is:
Schema::create('shops', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->string('phone')->nullable();
$table->string('address')->nullable();
$table->timestamps();
$table->string('description')->nullable();
$table->uuid('uuid');
});
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->integer('shop_id')->unsigned();
$table->foreign('shop_id')->references('id')->on('shops');
$table->string('name');
$table->string('code');
$table->string('slug');
$table->integer('price');
$table->uuid('uuid');
$table->timestamps();
});
There are only 2 ways of resolving this:
Either you pull in all products, and trim them in the end(advisable only if not too many products per shop):
$shops = Shop::with(['products' => function($subQuery) {
$subQuery
->with('pictures') //Running in scope of product, also load each product's pictures
->orderBy('created_at', 'desc');
}])
->get();
foreach ($shops as $shop) {
$shop->setRelation('products', $shop->products->take(3));
}
NOTE:
You will load every single product that is linked to the shops you load. You could get memory issues with this.
Take only what you need, but introduce a n+1 query issue(advisable only with small quantities of $shops:
$shops = Shop::get();
foreach ($shops as $shop) {
$shop->load([
'products' => function($query) {
$query
->orderBy('created_at', 'desc')
->limit(3)
->get();
}]);
}
NOTE:
N+1 query problem: You are performing a new query for each shop, so if you have a million shops, it will be a million extra queries.
EDIT: (answering comment question)
Q: How can i sort $shops based on their latest product created_at field?
$sortedShops = $shops->sortBy(function ($shop, $key) {
return $shop->products->first()->created_at;
})->values()->all();
sortBy is called on the collection(not uery). It allows you to go over each element(in this case shops) and use each object. Please do note that this function will fail if you have no products linked to the shop.
The ->values()->all() at the end makes sure that when you convert your shops to json, you will create an array, and not an object in js.
Source:
https://laravel.com/docs/7.x/collections#method-sortby
EDIT: (deleted original answer as it did not work)
Previous answer does not work, because limit(3) will limit the total amound of products loaded, in stead of 3 products per shop(my bad).

Refactor 3 parent child category relation queries with whereHas or another type of query?

So I have categories and channels table with a following relationship. A category hasMany channels. What I'm trying to do is get all channels that belong to a parents sub categories. I have one working attempt (Attempt 2 in controller) at this time and am wondering if I could make that into one query?
Channel Categories
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->integer('parent_id')->default(null);
Channels
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->integer('category_id');
Get All Channels By Category Slug Route
Route::get('/{channelCategory}', 'ChannelController#index');
ChannelController
public function index($channelCategory)
{
//Attempt 1. This works perfectly fine, but would like it to be in one query if possible
/*if($channelCategory->parent_id === 0){
$categories = ChannelCategory::where(['parent_id' => $channelCategory->id])->pluck('id');
$channels = Channel::whereIn('category_id', $categories)->get();
} else {
$channels = $channelCategory->channels;
}*/
//Attempt 2 whereHas Query.
//The problem is that it gets all posts from all parent categories instead of just one.
/*$channels = Channel::whereHas('category', function ($query) use ($channelCategory) {
$query->where('parent_id', $channelCategory->parent_id);
$query->orWhere('parent_id', null);
})->get(); */
return view('channels.home', compact('channels'));
}
Maybe what I am trying to do isn't possible with a whereHas. Is it possible to do the second attempt in one query and if so how?
I think you can do this by just eager loading the channels then mapping the category channels together:
$categories = ChannelCategory::with('channels')
->where('parent_id', $channelCategory->id)
->get();
return view('channels.home', [
'channels' => $categories->flatMap->channels
]);
Pagination would likely need to be done manually using the LengthAwarePaginator class:
$page = $request->get('page', 1);
$perPage = $request->get('perPage', 15);
$channels = $categories->flatMap->channels;
$items = $channels->forPage($page, $perPage);
$paginator = new LengthAwarePaginator($items, $channels->count(), $perPage, $page);
Getting the latest would involve sorting the collection and taking the limit desired:
$limit = $request->get('limit', 10);
$latest = collect($categories->flatMap->channels)->sortByDesc('created_at')->take($limit);

Reduce stock number in laravel

How can I reduce the number of stock in laravel?
In my products table i have stock column where i set numbers of item i have and i want to reduce it in 2 conditions:
1- when is added to order table (not when the product is in user cart)
2- when the order status is not cancelled (if order status become cancelled stocks increase back)
PS: currently I have no code for this matter that's why i didn't share
any code, I'm seeking for idea of how done it. Base on that i will
make functions and share here to complete it.
JSON of Order
"[{\"id\":29,\"name\":\"effewf\",\"price\":24524,\"quantity\‌​":1,\"attributes\":[‌​{\"attr\":{\"label\"‌​:\"Gray\",\"price\":‌​\"7000.00\"}}],\"con‌​ditions\":[]},{\"id\‌​":27,\"name\":\"new product\",\"price\":7246,\"quantity\":2,\"attributes\":[],\"‌​conditions\":[]}]"
UPDATE
base on #btl answer I added code below in my order model:
protected static function boot()
{
parent::boot();
$productIdsAndQuantities = $this->products->map(function ($product) {
return [$product->id => $product->quantity];
});
static::updating(function (Order $order) {
// restock if cancelled
$oldStatus = OrderStatus::find($order->getOriginal('orderstatus_id'));
if ($oldStatus->title !== 'Cancelled' && $order->orderstatus->title === 'Cancelled') {
$order->products->each(function(Product $product) {
$stock = $product->getAttribute('stock');
$product->update([
'stock' => $stock + $product->order->getAttribute($productIdsAndQuantities)
]);
});
}
});
}
I think this code needs fix in $productIdsAndQuantities and $product->update(['stock' => $stock + $product->order->getAttribute($productIdsAndQuantities)]); parts before i can use it.
currently i get error below when i try to add my cart detail to orders table
Using $this when not in object context
UPDATE 2
I changed my code to:
protected static function boot()
{
parent::boot();
static::updating(function (Order $order) {
$productIdsAndQuantities = $order->product_name->map(function ($product) {
return [$product->id => $product->quantity];
});
// restock if cancelled
$oldStatus = OrderStatus::find($order->getOriginal('orderstatus_id'));
if ($oldStatus->title !== 'Cancelled' && $order->orderstatus->title === 'Cancelled') {
$order->products->each(function(Product $product) {
$stock = $product->getAttribute('stock');
$product->update([
'stock' => $stock + $product->order->getAttribute($productIdsAndQuantities)
]);
});
}
});
}
now my order will be placed but nothing changes on stock column.
any idea?
Your tables look like,
Product
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
......
$table->integer('stock');
$table->timestamps();
});
Stock
Schema::create('stocks', function (Blueprint $table) {
$table->increments('id');
$table->integer('product_id')->unsigned();
$table->integer('order_id')->unsigned();
$table->enum('fall_rise',[-1,1]);
$table->timestamps();
$table->foreign('product_id')->references('id')->on('products');
$table->foreign('order_id')->references('id')->on('orders');
});
orders
Schema::create('orders', function (Blueprint $table) {
$table->increments('id');
.....
.....
$table->timestamps();
});
Now, add a sample product with stock value 10 like
$product = new Product();
$product->name = "abc";
......
$product->stock = 10;
$product->save();
Now, when abc is ordered, add details in order table. Now, stock value to be decreased then update value of stock column in product by 10-1 = 9, add record in stocks table like
Sample values,
product_id = 1;
order_id = 1;
....
fall_rise = -1;
In this way, when 10 stocks of the product are ordered then stock column becomes zero and hence product becomes unavailable. If you want to fetch only available products then use query like,
$available_products = Product::where('stock', '>', 0)->get();
I hope you will understand.
Register model events to handle updates on orders.
In your Order model:
protected static function boot()
{
parent::boot();
static::updating(function (Order $order) {
// restock if cancelled
$oldStatus = OrderStatus::find($order->getOriginal('orderstatus_id');
if ($oldStatus->name !== 'cancelled' && $order->orderstatus->name === 'cancelled') {
$order->products->each(function(Product $product) {
$stock = $product->getAttribute('stock');
$product->update(['stock' => $stock + $product->order->getAttribute('quantity_of_product_ordered')]);
});
}
// added to order table similar to above...
// check if it's a new product id being added to the order, decrease stock quantity accordingly...
// ...
});
}
SOLVED
the trick was to use try { on my CartController when I wanted to save my data in orders table there I should place my codes like:
try {
$order = new Order();
$qty = Cart::getTotalQuantity();
$order->product_data = $cartItems;
$order->user_id = Auth::user()->id;
// rest of it...
Auth::user()->orders()->save($order);
//trick is in this part
foreach ($cartItems as $item) {
$product = Product::find($item->id);
$product->decrement('stock', $item->quantity);
}
the answer was provided here

Laravel Model::find($id) doesn't filter results

I'm trying to edit a column of datatable to show informations in my index view table.
This is my controller's method that I call throught Ajax in the view:
public function getField(){
// Current User
$user_id = \Auth::user()->id;
$users = User::find($user_id)->users();
return Datatables::of($users )
->editColumn('id', 'Edit')
->editColumn('fields_id', function($users) {
$mystring = '';
$fields_id_array = json_decode($users->fields_id);
foreach($fields_id_array as $f_id){
/* this works */
// $mystring .= Fields::where('id', '=', $f_id) -> pluck('field_name');
/* this doesn't work */
$mystring .= Fields::find($f_id) -> pluck('field_name');
}
return $mystring;
})
->rawColumns(['id'])
->make(true);
}
The field 'fields_id' in users table is a JSON field like this: ['1','2','5'] and these refers to the id's of another table: 'Fields'.
I have already read this questions but I think my db table is it ok because increments are primary-key equivalent in Laravel migrations.
This is my migration for the table 'fields':
public function up()
{
Schema::defaultStringLength(191);
Schema::create('fields', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->string('code')->unique();
$table->string('param1');
$table->string('param3')->nullable();
$table->timestamps();
});
}
Thanks
a wild guess here, based on your last comment...
try this inside you loop
$mystring .= Fields::find(intval($f_id))->pluck('field_name');
I've added the intval() function arround your $f_id since you said you said they are in this format ['1','2','5']. that is an array of strings (that should be cast to integers if you plan to use them with find() function.

Laravel 5 Eloquent searching in relationship doesn't work

im trying to make a searchfield that returns records that are equal to the search result. I have 2 models, Brands and Cars. My relationship is that a brand(Opel, Suzuki etc.) has many cars and a car belongs to a brand.
Brand Model
class Brand extends Model
{
protected $fillable = [
'brandname'
];
public function cars(){
return $this->hasMany(Car::class);
}
}
Cars Model
class Car extends Model
{
public function brand(){
return $this->belongsTo(Brand::class);
}
protected $fillable = [
'user_id', 'brand_id', 'year', 'licensplate'
];
}
I have a page that shows all the cars with some information.
Find Cars form
This is de code of the page:
<div class="col-sm-8 blog-main">
<h1>Alle auto's</h1><hr>
<form method="POST" action="/allcars">
{{csrf_field()}}
<div class="form-group">
<input type="text" name="searchfield" placeholder="Merk">
<button type="submit">Zoek</button>
</div>
</form>
#foreach($brands as $brand)
#foreach($brand->cars as $car)
<div class="form-group">
<h5>
{{$brand->brandname}}
</h5>
Kenteken: {{$car->licensplate}}<br>
Bouwjaar: {{$car->year}}<br>
</div>
#endforeach
#endforeach
</div>
This is my route file:
Routes
Route::post('/allcars', 'CarController#show');
Finally my controller file:
CarController.php
public function show(){
$input = request('searchfield');
if(empty($input))
{
return back();
}
else if(is_numeric($input))
{
// Search on year
$brands = Brand::with('cars')->whereHas('cars', function($q)
{
$input = request('searchfield');
$q->where('year', $input);
})->get();
return view('content.cars.index', compact('brands'));
}
else
{
// Search on brandname
$brands = Brand::with('cars')->get()->where('brandname', $input);
return view('content.cars.index', compact('brands'));
}
}
The else if statement is working perfectly. When an integer (year) is being filled in it uses the year search and when a string is filled in it uses the brand search.
However, the brand search is working perfectly. If I search on 'Opel', i get all the cars with some information that are from the brand Opel. But when I search on 1990, I get a brand that has a car with the year 1990, and then i get the other cars from that brand as well.
Example:
If I search for 1990, I get 3 cars: Opel(1990), Opel(2000) and Opel(1882).
An other example:
I search on 2000, I get 5 cars: Opel(2000), Opel(1990), Opel(1882), BMW(2000), BMW(1721).
I think I am doing something wrong in my code, please correct me.
These are my migrations btw:
Car Migration
Schema::create('cars', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedInteger('brand_id');
$table->foreign('brand_id')->references('id')->on('brands')->onDelete('cascade');
$table->string('year');
$table->string('licensplate');
$table->timestamps();
});
Brand Migration
Schema::create('brands', function (Blueprint $table) {
$table->increments('id');
$table->string('brandname');
$table->timestamps();
});
Try changing the else if part to this:
else if(is_numeric($input))
{
// Search on year
$brands = Brand::with(['cars' => function ($q) use ($input) {
$q->where('year',$input);
}])->get();
return view('content.cars.index', compact('brands'));
}

Resources