I used self-referential model to build nested category:
class Category extends Model {
public function parent()
{
return $this->belongsTo('Category', 'parent_id');
}
public function children()
{
return $this->hasMany('Category', 'parent_id');
}
// recursive, loads all descendants
public function childrenRecursive()
{
return $this->children()->with('childrenRecursive');
}
}
and to get parents with all their children:
$categories = Category::with('childrenRecursive')->whereNull('parent')->get();
But i've faced performance issues for my large table (3 level nested category)
Is there any better way to retrieve categories?
And my second question. assume that my categories is something like this:
- Digital
- Mobiles
- Power Banks
- Cover & Bumper
- Computers
- Health
- Bar
And my posts can be in any category like Digital or Mobiles or Powerbank
When user ask me about Digital category i should show my users all of my post in this category or in child categories of Digital.
And when ask me about Mobiles i should show my users all of my post in this category or in child categories of Mobile
How can i do it?
$categoryWithNestedSubCategoriesAndProducts = Category::with(['products', 'childrenRecursive', 'childrenRecursive.products'])->where('id',$id)->get()->toArray();
Flatten laravel recursive relationship collection
First, we should Flatten our laravel recursive relationship collection (tree collections) using function like
public function flatten($array)
{
$flatArray = [];
if (!is_array($array)) {
$array = (array)$array;
}
foreach($array as $key => $value) {
if (is_array($value) || is_object($value)) {
$flatArray = array_merge($flatArray, $this->flatten($value));
} else {
$flatArray[0][$key] = $value;
}
}
return $flatArray;
}
Get list of products
$categoryWithNestedSubCategoriesAndProducts = Category::with(['products', 'childrenRecursive', 'childrenRecursive.products'])->where('id', $id)->get()->toArray();
$flatten = $this->flatten($categoryWithNestedSubCategoriesAndProducts);
foreach ($flatten as $key => $fl) {
// eliminate categories from $flatten array
if (!array_key_exists('category_id', $fl)) {
unset($flatten[$key]);
}
}
$products = array_values($flatten);
This is the method I used for my project.
I get a list of all the categories and then control them. I didn't find any suitable way to use eloquent in this case.
I recommend reading the link below.
hierarchical data
Related
I am trying to create Laravel/Vue project with two models: Category and Article. Vue part haves tree-view, which will display categories and articles tree. Categories may belong to another categories, Article may belong only to Article.
How can i form json tree from these relations?
model Category
public function articles() {
return $this->hasMany(Article::class);
}
public function childs() {
return $this->hasMany(Category::class)->union($this->files()->toBase());
}
but it shows The used SELECT statements have a different number of columns, because there is defferent fields in results.
One solution i see here is to find every article and post and create array, then jsonify it. Maybe any better solutions?
UPDATE
Done it with this code (in api controller):
public function nodes() {
$rootCategories = Category::where('category_id', null)->get();
$out = $this->_nodes($rootCategories);
return response()->json($out);
}
private function _nodes($eCategories) {
$out = [];
foreach($eCategories as $cat) {
$out[$cat->id] = $cat->toArray();
$out[$cat->id]["type"] = "folder";
$out[$cat->id]["childs"] = [];
foreach ($cat->articles as $article) {
$out[$cat->id]["childs"][$article->id] = $article->toArray();
$out[$cat->id]["childs"][$article->id]["type"] = "article";
}
if ($cat->categories) {
$out[$cat->id]["childs"] = $out[$cat->id]["childs"] + $this->_nodesCategory($cat->categories);
}
}
return $out;
}
I have an array of category , and this categories have many books (belongToMany) how i can get all book
sorry about my English
category model
class category_b extends Model
{
protected $table = 'categoryb';
protected $attributes = array(
'lang'=> 'fa',
);
public function getBooks()
{
return $this->belongsToMany('App\book','categoryb_book');
}
}
my book model
class book extends Model
{
public function getCategory()
{
return $this->belongsToMany('App\category_b','categoryb_book');
}
}
my code
$Category = $user->subscriptions(category_b::class)->pluck('id');
$bookCategory= category_b::whereIn('id',$Category)->get();
$bookCategory = $bookCategory->getBooks;
As #Makdous indicated, different approaches exist, you may take the one that fits your need.
Notice that the loop approach may lead to duplicate books, so after the loop you will have to delete duplicate records.
To avoid that problem, one solution would be to query the Book model directly:
$categoryIds = $user->subscriptions(category_b::class)->pluck('id');
$books = book::whereHas('getCategory', function ($q) use ($categoryIds) {
return $q->whereIn('categoryb.id', $categoryIds);
})
->get();
EDIT:
You may go beyond that and improve the readability of your code by defining a local scope.
In your Book class:
class book extends Model
{
// .....
// .....
// Somewhere in your Book class ...
public function scopeOfCategories($query, $categoryIds = [])
{
return $query->whereHas('getCategory', function ($q) use
($categoryIds) {
return $q->whereIn('categoryb.id', $categoryIds);
});
}
}
Now you can replace the old code snippet with the following:
$categoryIds = $user->subscriptions(category_b::class)->pluck('id');
$books = book::ofCategories($categoryIds)->get();
You can use a foreach as mentioned in the docs to iterate through the books of you're fetched category.
foreach ($bookCategory as $book) {
//access book's attributes
}
Or likewise you can get the categories of a certain book.
$book = Book::find($id);
$categories = $book->getCategory();
foreach ($categories as $category) {
//access category's attributes
}
I have the tables "item", "payment", "join_payment".
The item has an id, the payment has an id. The join_payment has the rows item_id and payment_id.
A payment may contain many items, which would be registered in the join_payment table.
I want to make a log with these items and I am currently doing this in the controller:
$log = Array();
$items = item::where("id",Auth::user()->id)->get();
foreach($items as $item){
$join_payment = join_payment::where("item_id",$item->id)->first();
if(!array_key_exists($join_payment->payment_id,$log){
$log[$join_payment->payment_id] = payment::where("id",$join_payment->payment_id)->first();
}
$log[$join_payment->payment_id][$item->id] = $item;
}
Is there a way to pull this out with the models?
I recommend using Eloquent relationships for this. https://laravel.com/docs/5.5/eloquent-relationships#many-to-many. If you call the join table item_payment it will be even easier:
class Item extends Model {
public function payments(){
return $this->belongsToMany(Payments::class)
}
}
class Payment extends Model {
public function items(){
return $this->belongsToMany(Item::class)
}
}
class ItemPayment extends Model {
public function item(){
return $this->belongsTo(Item::class)
}
public function payment(){
return $this->belongsTo(Payment::class)
}
}
Then you can access the data you need in a bunch of ways:
$items = Item::all();
foreach($items as $item){
$item->payments; //returns all payments belonging to this item
}
$payments = Payment::all();
foreach($payments as $payment){
$payment->items; //returns all items belonging to this payment
}
$itemPayments = ItemPayment::all();
foreach($itemPayments as $itemPayment){
$itemPayment->item; //the item for this join
$itemPayment->payment; //the payment for this join
}
Sorry for changing your class and table names, but these conventions will make your life a ton easier in Laravel
in your Item model use this
public function payment()
{
return $this->hasOne('App\Payment','join_payment','payment_id','item_id');
}
then in you loop check
foreach($items as $item){
dd($item->payment);
}
I have 3 models: Shop, Products and Tags. Shop and Products are in one to many relation, and Products to Tags many to many.
I want to grab for each Shop all unique Tags (since many products can have same tags).
class Shop extends Eloquent {
public function products() {
return $this->hasMany('Product');
}
}
class Product extends Eloquent {
public function shop() {
return $this->belongsTo('Shop');
}
public function tags() {
return $this->belongsToMany('Tag');
}
}
class Tag extends Eloquent {
public function products() {
return $this->belongsToMany('Product');
}
}
One of the solutions that I came up with is following. Problem is that I don't get unique tags. There is a solution to put another foreach loop to go thru tags array and compare id in tag object. I would like to optimize a little bit, what do you think is better/cleaner solution?
class Shop extends Eloquent {
...
public function getTagsAttribute() {
$tags = array();
foreach($this->products as $product)
{
foreach ($product->tags as $tag)
{
$tags[] = $tag;
}
}
return $tags;
}
}
#WereWolf's method will work for you, however here's a trick that will work for all the relations:
$shop = Shop::with(['products.tags' => function ($q) use (&$tags) {
$tags = $q->get()->unique();
}])->find($someId);
// then:
$tags; // collection of unique tags related to your shop through the products
Mind that each of the $tags will have pivot property, since it's a belongsToMany relation, but obviously you don't rely on that.
Probably you may try this:
$tags = Tag::has('products')->get();
This will return all the Tags that's bound to any Product. If necessary, you may also use distinct, like this, but I think it's not necessary in this case:
$tags = Tag::has('products')->distinct()->get();
Update: Then you may try something like this:
public function getTagsAttribute()
{
$shopId = $this->id;
$tags = Tag::whereHas('products', function($query) use($shopId) {
$query->where('products.shop_id', $shopId);
})->get();
return $tags;
}
What's the minimum amount of queries needed for this operation?
category->hasMany(post)
post->belongsTo(user)
The only thing I can come up with is:
$categories = Category::all();
foreach($categories as $category) {
$category->posts = Post::where('category_id', $category->id)->take(4)->get();
}
Say I got 4 categories the output of the query log will print 9 line of queries.
here's a quick example of what I mentioned in the comments. By defining a method on your Category model, you can reduce the number of calls you make for each category to 1:
(note: this is a many-to-many relationship)
Category model (Category.php)
class Category extends Eloquent {
public function posts()
{
return $this->belongsToMany("Post");
}
public function getRecentPosts($n)
{
return $this->posts()->orderBy('created_at')->take($n)->get();
}
}
Posts model (Post.php)
class Post extends Eloquent {
public function categories()
{
return $this->belongsToMany("Category");
}
}
Then you can just call the getRecentPosts($n) method to get $n posts!
foreach (Category::all() as $category)
{
$posts = $category->getRecentPosts(4);
foreach ($posts as $post)
{
echo $post->name . "<br/>";
}
}
You will be making $n + 1 queries.. one to get the Category::all() and then one for the Posts of each Category...
hope it helps!