Get next / prev element in Eloquent - php

I have a model for Product and Catalogue. They have many to many relationship, therefore I am using a pivot table called 'items'. I have a route:
/something/something/{catalogue}/{product}
I am passing the product model and catalogue model to the view, which outputs the product information and catalogue information - pretty straight forward. Now I need to have 2 buttons - 'next' and 'previous' to navigate through products in the catalogue.
So one way to do this, I thought I would create 2 methods on the Catalogue model that would accept the current product model and return next / prev model based on ID:
public function prevProduct($product){
$prevProdId = Product::where('id', '<', $product->id)->max('id');
return Product::find($prevProdId);
}
public function nextProduct($product){
$nextProdId = Product::where('id', '>', $product->id)->min('id');
return Product::find($nextProdId);
}
Now this works fine, but as you can see, it retrieves the next product and previous product from the Product table in the database, not the catalogue.
To get all the products in the catalogue, can be done like so: $this->items (on Catalogue model) or $catalogue->items (from view).
I somehow need to get next / prev items from the Catalogue, but can't figure out how. Any suggestions would be much appreciated.

You can filter Collection, like this:
$next = $item->id + 1;
$catalogue->filter(function($item) use ($next ) {
return $item->id == $next;
})->first();
I use global method added to Collection class:
/**
* Returns first object from collection which meets attribute criteria
*
* #param string $attributeName name of attribute we are looking for
* #param string $attributeValue value of attribute that have to be met
* #return \Illuminate\Database\Eloquent\Collection
*/
public function findByAttribute($attributeName, $attributeValue)
{
return $this->filter(function($item) use ($attributeName, $attributeValue) {
return $item->{$attributeName} == $attributeValue;
})->first();
}
In this case I would use this method this way:
$catalogue->items->findByAttribute('id', $next);
In both examples I assume You have $item object to reffer to.

You can use Pagination
Catalogue::first()->items()->paginate(1);

Actually, it was much simpler. This did the trick:
$nextProdId = $this->items()->where('product_id', '>', $product->id)->min('product_id');
return Product::find($nextProdId);
I had to add () after 'items' to enable 'where' clause and make it searchable basically.

Related

Foreach on value blade Laravel [duplicate]

This question already has answers here:
How can i iterate over attributes in laravel models?
(2 answers)
Closed last year.
I have a problem with Laravel when I try to display my data via my view...
Here is my code to browse everything with my view :
#foreach($recipes as $t=>$d)
{{$t}} // doesn't display all the key
#endforeach
{{$recipes->LABEL}} // work great
Their is the result : incrementing preventsLazyLoading exists wasRecentlyCreated timestamps
Their is my controller and the model :
Model :
/**
* Get a recipe from its primary id : recipe_id
* If fail it return 404 exception
* Else return the collection with all the data of a recipe
*
* #param integer $id id of the recipe in the db
* #return Collection the collection that have been finded by his id
*/
public static function getRecipe($id)
{
return self::findOrFail($id);
}
Controller :
function getRecipe($id)
{
return view('recipe_detail')->with('recipes', Recipe::getRecipe($id));
}
Is their any solution to display with foreach and with call the key ?
Thanks
You have a single object $recipe that you are trying to loop on in your blade file.
This method:
public static function getRecipe($id)
{
return self::findOrFail($id);
}
returns the single object based on the $id. If you want a collection, skip the $id and use get() or all(), something like:
public function getRecipes(){
return self::all()
}
Or, just call it right in the controller without need for the method in the Model: $recipes = Recipe::all();
You can then loop on $recipes in the blade file because it is now a collection.
If you want to get all the "attributes" of the Model then you have to ask the Model for them; you can use the getAttributes method:
#foreach ($model->getAttributes() as $key => $value)
...
#endforeach

Laravel filter data after with closure

I have one quite simple question, Imagine I have Orders model and now I am writing something like that :
Order::where('status', 1)->with('orderer')->get();
Ok. It's simple and returns something like that:
{
id: 1,
price: 200,
status: 1,
income: 21,
orderer_id: 4,
orderer: {
//some orderer fields
}
}
now I don't want to get the whole object, I want to remove income, orderer_id and status properties from data. if I write something like that : get(["id", "price"]) I end up without orderer object (get(["id", "price", "orderer"]) doesn't work too), I couldn't make it work even using select(), so what is the solution? Also I don't want to hide it from everyone, for example admin should know income but user shouldn't, so $hidden field will not work.
You can add select() but make sure select does not take array but comma separated arguments :
$orders = Order::where('status', 1)->with('orderer');
if($user->role == 'admin'){
$orders->select('id','income','status','price');
}
else{
$orders->select('id','status','price');
}
$orders = $orders->get();
Above will first check the current logged in user's role and accordingly will select the columns required.
https://scotch.io/bar-talk/hiding-fields-when-querying-laravel-eloquent-models
In your Order Eloquent model:
protected $hidden = array('hide_this_field', 'and_that_field');
Edit: You want to filter based on role like Admin or User, next time please write that down in your question as well. Well a solution for that is to capture the DB query result, and walk that array, then unset properties of the model if the user is not an admin.
Edit2: I also see a discussion here which might help. Some user suggested using middle ware:
https://laracasts.com/discuss/channels/laravel/hide-eloquent-fields-based-on-user-role-or-any-model
If you are looking for a built in Laravel way to handle this, you could use API Resources: https://laravel.com/docs/5.7/eloquent-resources
php atrisan make:resource OrderResource
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$current_role = $request->user()->role; //or however you determine admin etc
$out = [
'id' => $this->id,
'price' => $this->price,
'orderer'=> $this->orderer,
];
if($current_role == 'admin'){
$out['income'] = $this->income;
$out['status'] = $this->status;
}
return $out;
}
}
In your Controller action
return OrderResource::collection(Order::where('status', 1)->with('orderer')->get());
If you want something a little more robust, consider https://github.com/spatie/laravel-fractal

Laravel - Get value associated with product

In my project, I'm working with polymorphic relations which I find very hard to understand. Do have in mind that I am a beginner in programming. My Database looks something like this:
Themes
id - integer
composer_package - string
name - string
Plugins
id - integer
composer_package - string
name - string
Products
id - integer
name - string
...
productable_id - integer
productable_type - string
In the store method below. I am getting the ID of the selected theme. Then I find that theme by doing $theme = Product::find($selectedTheme);. The $selectedTheme is the ID of the theme. I have an Array which is called predefinedArray which contains all the fillable fields of theme. Then It puts all the values that that specific theme has in a session called chosen_theme.
public function store(Request $request)
{
$selectedTheme = null;
foreach($request->input('theme') as $key => $value) {
if($value === 'selected') {
$selectedTheme = $key;
}
}
$theme = Product::find($selectedTheme);
foreach($this->predefinedArray as $value) {
$request->session()->put('chosen_theme.' . $value, $theme->$value);
}
$data = $request->session()->all();
return redirect('plugins');
}
The theme is a product. I need to get the composer_package that is associated with it and put it in the request. Say, for instance, I find a theme with the ID 20, This theme's productable_id is 5. Then I need to get the composer_package in the Themes table where the ID is 5 and put it inside the request. How can I achieve that? The array currently looks like this:
As you can see, The composer_package field is empty, This needs to be filled with the composer_package that is associated with the selected product.
My models look like this:
Product
public function productable()
{
return $this->morphTo();
}
public function order_items()
{
return $this->hasMany(Orderitems::class);
}
Theme
public function webshops()
{
return $this->hasMany(Webshop::class);
}
public function products()
{
return $this->morphMany(Product::class, 'productable');
}
How can I achieve this? Thanks in advance!
When you are doing this
$theme = Product::find($selectedTheme);
You are loading the data from the product table. The composer_package field is however not stored in the product table, but in the morphed row from the theme / plugin table.
To access that value you need to do $theme->productable->composer_package
A quick and dirty way of doing that might be this:
foreach($this->predefinedArray as $value) {
$request->session()->put('chosen_theme.' . $value, $theme->$value);
}
$request->session()->put('chosen_theme.composer_package', $theme->productable->composer_package);

Eloquent Collections, how to modify the data of each registry contained in results?

I have the following situation, Im trying to modify the price of products displayed in a platform.
Everything works ok for only 1 product (eg: product view) but I dont know what I have to do in order to modify the price of each product in an eloquent collection.
this is the code in my app:
ProductRepository.php:
public function CalcPrice($product){
$x = $product->price; //eg 5
$y = 4;
$amount= $x + $y;
return $amount;
}
For the details view of each product inside ProductController I have the following code and everything works perfect:
public function details($id){
$product = $this->product->getProductById($id);
$productprice = $this->product->getCalcPrice($product = $product);
return view('products.view',compact('product','productprice'))
}
On the other hand, my idea is to use the code contained in ProductRepository.php function CalcPrice in a collection.
My main doubt is what do I have to do, because in a collection probably I can have a variable $category in order to retrieve all products in a category, but I will not have a variable for each $product (for eg: a $productid like in details).
What can I do in order to eg:
modify each product price contained in a collection of a category
using CalcPrice function code?
eg: of code:
productrepository.php
public function AllProductsInCategory($catid)
{
return App\Product::where('categoryid', $catid)
->get();
}
but each product displaying their ($product->price + 4) as CalcPrice performs. thanks!.
You can achieve this by defining an attribute accessor on model and append it to model. This way it would be available for you on each instance like its other attributes.
As Taylor Otwell mentioned here, "This is intentional and for performance reasons." However there is an easy way to achieve this, say you have model named Product;
class Product extends Eloquent {
protected $appends = array('calc_price');
public function getCalcPriceAttribute()
{
//if you want to call your method for some reason
return $this->getCalcPrice($this);
// Otherwise more clean way would be something like this
// return $this->price + 4 // un-comment if you don't want to call getCalcPrice() method
}
}
Now you can access calculated price on each $product by simply calling $product->calc_price.

Magento get related products not having all attributes loaded

i'm taking over a project and saw that the previous developer added a custom related products association. So he implemented a function to get the associated collection looking like this
/**
* Retrieve collection CustomRelated product
*
* #return Mage_Catalog_Model_Resource_Product_Link_Product_Collection
*/
public function getCustomRelatedProductCollection()
{
$collection = $this->getLinkInstance()->useCustomRelatedLinks()
->getProductCollection()
->setIsStrongMode();
$collection->setProduct($this);
return $collection;
}
Then in phtml file, he's calling it out like this
$upsell_products = $_product->getCustomRelatedProductCollection();
And then he uses that collection in a foreach, and each element in the collection is using model 'catalog/product', but somehow it's not loading enough attributes like prices and name
It will load all of the attributes only when i call load function again like this
Mage::getModel('catalog/product')->load($p->getId())
Which i don't want to do because it's pointless to reload the model, i'm still new to Magento so i'm not sure how to make the get collection above to fully load the product model, any ideas?
You can load require attributes (name, price) like below.
public function getCustomRelatedProductCollection()
{
$collection = $this->getLinkInstance()->useCustomRelatedLinks()
->getProductCollection()
->addAttributeToSelect(array("name", "price"))
->setIsStrongMode();
$collection->setProduct($this);
return $collection;
}
//I have added new line in your code. please check now.
public function getCustomRelatedProductCollection()
{
$collection = $this->getLinkInstance()->useCustomRelatedLinks()
->getProductCollection()
->setIsStrongMode();
$collection->setProduct($this);
$collection->addAttributeToSelect('*'); //New line added by me.
return $collection;
}

Categories