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;
}
Related
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 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
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 am having articles and features tables and a pivot article_feature table. Features table have items like commenting, like, share. I also have tables comments and likes .
In my function I should get all features for an article and then get all the results from relationships. Basically tell laravel get me all features and for each feature give me all results. The problem is that features table is not connected with tables like comments and likes. I was wondering if that is possible with hasManyThrough but not sure how to do it since there could be many features. I have set up relationships in Article model like this:
public function comments()
{
return $this->hasMany('App\Comment');
}
public function features()
{
return $this->belongsToMany('App\Feature');
}
public function likes()
{
return $this->hasMany('App\Like');
}
This is my function:
public function latest(){
$result = Article::with('comments.user')->where('publish', 1)->orderBy('created_at', 'desc')->paginate(15);
$user = JWTAuth::parseToken()->authenticate();
foreach($result as $article){
$articles[$article->id] = $article;
$articleFeatures = $article->features()->get()->toArray();
if (count($articleFeatures) != 0) {
$articles[$article->id]['features'] = $articleFeatures;
$articles[$article->id]['likes'] = $article->likes()->get();
$articles[$article->id]['comments'] = $article->comments()->get();
}
return $articles;
}
A user can save ingredients to a shopping list. Now, when a User is logged in and visit a recipe, the shopping list widget is showing him, which ingredients he already have on his list, and that's my problem.
Here I'm getting all ingredients for the recipe:
$recipe = Recipe::find($id);
$recipe->load('ingredients');
Working fine with a foreach on $recipe->ingredients
And here I'm gettingthe shopping list a user has for this recipe, if he has one:
if(Auth::check()) {
$list = ShoppingList::with('ingredients')->where('recipe_id',$id)->where('user_id',Auth::user()->id)->get();
//...
}
And here I'm trying to check, if an saved ingredient is on the shopping list:
foreach($recipe->ingredients as $i) {
foreach($list as $l) {
$ingredient = Ingredients::where('id','=',$l->ingredients_id)->get();
$i->isAdded = (count($ingredient) > 0) ? 1 : 0;
}
}
But somehow that's totally wrong. What am I missing?
Relationships:
Recipe:
public function user() {
return $this->belongsTo('User','user_id');
}
public function lists() {
return $this->hasMany('ShoppingList','recipe_id');
}
public function ingredients() {
return $this->belongsToMany('Ingredients','ingredients_recipe','recipe_id','ingredients_id')->withPivot('amount');
}
Shopping List:
public function user() {
return $this->belongsTo('User','id');
}
public function recipe() {
return $this->belongsTo('Recipe','recipe_id');
}
public function ingredients() {
return $this->belongsToMany('Ingredients','shopping_list_ingredients','shopping_list_id','ingredients_id')
->withPivot(array('unit','amount'))
->withTimestamps();
}
Ingredients:
public function recipes() {
return $this->belongsToMany('Recipe','ingredients_recipe','recipe_id','ingredients_id')->withPivot('amount');
}
public function lists() {
return $this->belongsToMany('ShoppingList','shopping_list_ingredients','shopping_list_id','ingredients_id')
->withPivot(array('unit','amount'))
->withTimestamps();
}
What am I doing wrong? Thank you!
Your explanation is a bit cloudy with this code, but the point is, that you need to find missing ingredients and add them to the shopping list, is it correct?
For this you can use Collection's diff method:
// retrieve collection of the ingredients for a recipe
$recipe = Recipe::with('ingredients')->find($recipeId);
// retrieve collection of the ingredients for a shopping list
$list = ShoppingList::with('ingredients')->find($recipeId);
// find the ingredients for the recipe that are not on the list already
// return Collection of Ingredient models
$missingIngredients = $recipe->ingredients->diff($list->ingredients);
// and ingredients on both list and recipe
$missingIngredients = $recipe->ingredients->intersect($list->ingredients);