Laravel Recursive query with eloquent - laravel

I have the following table in mysql:
Schema::create('tb__menu', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('idmenu')->nullable();
$table->string('name', 250);
$table->foreign('idmenu')->references('id')->on('tb__menu');
$table->engine = "InnoDB";
});
Here some records:
id | idmenu | name
1 NULL root
2 1 folder 1
3 1 folder 2
4 2 folter 1 A
5 2 folder 1 B
6 2 folder 1 C
7 3 folder 2 A
8 4 folder 1 A A
This is the model of the table:
class Menu extends Model
{
protected $table = 'tb__menu';
protected $fillable = [
'id',
'idmenu',
'name',
];
protected $casts = [
'id' => 'integer',
'idmenu' => 'integer',
'name' => 'string'
];
public function menu() {
return $this->belongsTo(Menu::class, 'idmenu');
}
public function menus() {
return $this->hasMany(Menu::class, 'idmenu');
}
}
And this is the controller:
class MenuTC extends Controller
{
public function index()
{
return /* NEED */
}
}
How can I get a json structure of the menu table records?, for example:
[
{
"id": 1,
"name": "root",
"childrens": [
{
"id": 2,
"name": "folder 1",
"childrens": [
{
"id": 4,
"name": "folder 1 A",
"childrens": [
{
"id": 8,
"name": "folder 1 A A"
}
]
},
{
"id": 5,
"name": "folder 1 B"
},
{
"id": 6,
"name": "folder 1 C"
}
]
},
{
"id": 3,
"name": "folder 2",
"childrens": [
{
"id": 7,
"name": "folder 2 A"
}
]
}
]
}
]
I would appreciate any help you can give me.
PD: In SQL server, I could do this with a recursive query, but in Laravel I have no idea how to do it.

create a recursive relationship like below
public function childMenu(){
return $this->hasMany(Menu::class,'idmenu','id');
}
public function recursiveChildMenu(){
return $this->childMenu()->with('recursiveChildMenu');
}
then in controller
Menu::with('recursiveChildMenu')->get();

Related

Not getting expected data for Laravel JSON:API

I am trying to create an API based on the following two Models:
Product.php
<?php
namespace App\Models\Commerce;
use ...
class Product extends Model {
use HasFactory;
use SoftDeletes;
public mixed $id;
protected $fillable = ['user_id', 'title', 'default_variation', 'status'];
/**
+ * #return HasMany
+ */
public function productVariations() : HasMany {
return $this->hasMany('App\Models\Commerce\ProductVariation');
}
public function defaultVariation() : HasOne {
return $this->hasOne('App\Models\Commerce\ProductVariation', 'default_variation');
}
}
ProductVariation.php
<?php
namespace App\Models\Commerce;
use ...
class ProductVariation extends Model {
use HasFactory;
protected $fillable = ['user_id', 'product_id', 'sku', 'title', 'price', 'status'];
public function product() : BelongsTo {
return $this->belongsTo(Product::class);
}
}
I have configured the JSON:API according to the documents, as best I can tell:
routes/api.php
<?php
use ...
JsonApiRoute::server('v2')->prefix('v2')->resources(function ($server) {
$server->resource('products', JsonApiController::class)->readOnly();
$server->resource('product-variations', JsonApiController::class)->readOnly();
});
ProductSchema.php
<?php
// For authentication see ProductPolicy
namespace App\JsonApi\V2\Products;
use ...
class ProductSchema extends Schema
{
public static string $model = Product::class;
public function fields(): array
{
return [
ID::make(),
DateTime::make('createdAt')->sortable()->readOnly(),
DateTime::make('updatedAt')->sortable()->readOnly(),
Str::make('title'),
Number::make('user_id'),
Number::make('defaultVariation'),
HasOne::make('defaultVariation', 'default_variation'),
Boolean::make('status'),
HasMany::make('productVariations')
];
}
public function filters(): array
{
return [
WhereIdIn::make($this),
];
}
public function pagination(): ?Paginator
{
return PagePagination::make();
}
}
ProductVariationSchema.php
<?php
namespace App\JsonApi\V2\ProductVariations;
use ...
class ProductVariationSchema extends Schema
{
public static string $model = ProductVariation::class;
public function fields(): array
{
return [
ID::make(),
DateTime::make('createdAt')->sortable()->readOnly(),
DateTime::make('updatedAt')->sortable()->readOnly(),
Number::make('userId'),
Number::make('productId'),
Str::make('sku'),
Str::make('title'),
Number::make('price'),
Boolean::make('status'),
BelongsTo::make('product')
];
}
public function filters(): array
{
return [
WhereIdIn::make($this),
];
}
public function pagination(): ?Paginator
{
return PagePagination::make();
}
}
With some dummy data in the database, I can access the endpoint /api/v2/products and receive the following:
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "products",
"id": "3",
"attributes": {
"createdAt": null,
"updatedAt": null,
"title": "foo-product",
"user_id": 1,
"status": 1
},
"relationships": {
"defaultVariation": {
"links": {
"related": "domain/api/v2/products/3/default-variation",
"self": "domain/api/v2/products/3/relationships/default-variation"
}
},
"productVariations": {
"links": {
"related": "domain/api/v2/products/3/product-variations",
"self": "domain/api/v2/products/3/relationships/product-variations"
}
}
},
"links": {
"self": "domain/api/v2/products/3"
}
}
]
}
/api/v2/product-variations
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "product-variations",
"id": "3",
"attributes": {
"createdAt": null,
"updatedAt": null,
"userId": 1,
"productId": 3,
"sku": "foo-bar",
"title": "foo bar ",
"price": "11.11",
"status": 1
},
"relationships": {
"product": {
"links": {
"related": "domain/api/v2/product-variations/3/product",
"self": "domain/api/v2/product-variations/3/relationships/product"
}
}
},
"links": {
"self": "domain/api/v2/product-variations/3"
}
}
]
}
However, if I try api/v2/products/3/product-variations or /api/v2/products/3/relationships/product-variations I get a 404 error:
{
"jsonapi": {
"version": "1.0"
},
"errors": [
{
"status": "404",
"title": "Not Found"
}
]
}
Additionally, if I run a test I also get a 404 but I do see that the variations are correctly related:
I think that somewhere I have misconfigured something, but this is my first time doing this in Laravel so I might be completely missing a configuration also.
You only set a JsonApiRoute (starting with api) and prefix('v2') meaning api/v2 group. In this group you add 2 more product-variations meaning /api/v2/product-variations and products /api/v2/products. So anything else don't exists and laravel returns 404.
You need to add
$server->resource('products/{id}/product-variations', JsonApiController::class)->readOnly();
for api/v2/products/3/product-variations
and
$server->resource('products/{id}/relationships/product-variations', JsonApiController::class)->readOnly();
for /api/v2/products/3/relationships/product-variations
Also you can add (int $id) in your controller methods to read this id you pass.

How to merge object and array in one controller laravel

I got like this
[
{
"NameProduct": "iphone",
"IDProduct": 1,
"ListPrice": 100000,
"ImagePath": "iphone.png"
},
[
{
"IDCollection": 5,
"NameCollection": "Phone",
"Description": "my description",
}
],
{
"NameProduct": "SamSung",
"IDProduct": 2,
"ListPrice": 379000,
"ImagePath": "samsung.png"
},
[
{
"IDCollection": 5,
"NameCollection": "Phone",
"Description": "my description",
}
],
But I want like this:
{
"IDCollection": 5,
"NameCollection": "Phone",
"ProductList": [
{
"NameProduct": "iphone",
"IDProduct": 1,
"ListPrice": 100000,
"ImagePath": "iphone.png"
},
{
"NameProduct": "SamSung",
"IDProduct": 2,
"ListPrice": 100000,
"ImagePath": "samsung.png"
},
]
}
This is my code:
public function show(int $id)
{
$product=[];
$product_collection = CollectionProduct::where('IDCollection',$id)->get();
$collection = Collection::where('IDCollection',$id)->get();
foreach ($product_collection as $items) {
$x = Product::select('NameProduct','IDProduct','ListPrice')->find($items['IDProduct']);
$x->ImagePath = ProductImage::where('IDProduct',$items['IDProduct'])->first()['Path'];
array_push($product, $x,$collection);
}
return response()->json($product);
}
and idea how do that ?
You can use Eager Loading to get the relationships of CollectionProduct. https://laravel.com/docs/9.x/eloquent-relationships#eager-loading
For help you with the code I need to see your relationships of the DB but I think this can help you:
public function show(int $id)
{
$product_collection = CollectionProduct::with('collection.product.productImage')
->where('IDCollection',$id)
->get();
return response()->json($product_collection);
}

Laravel - how to group by pivot table column

I have a many to many relationship with one extra column in pivot table i.e module_id, i want to eager load the relationship data groupBy('module_id'). I tried couple of thing that didn't work, I successfully managed to get the desired structure but not through the eager loading (right way), i directly called the pivot table model queried the table. The problem is i cannot query pivot table directly becuase i have implemented the repository pattern.
This is how i queries the pivot directly.
$selectedGroups = RoleGroup::select('role_id', 'module_id', 'group_id')->where('role_id', $role->id)->get()->groupBy('module_id');
Migration:
Schema::create('role_groups', function (Blueprint $table)
{
$table->id();
$table->unsignedBigInteger('role_id');
$table->unsignedBigInteger('module_id');
$table->unsignedBigInteger('group_id');
$table->timestamps();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade');
});
Role Model:
public function groups()
{
return $this->belongsToMany(Group::class, 'role_groups')->withPivot('module_id')->withTimestamps();
}
Group Model:
public function roles()
{
return $this->belongsToMany(Role::class, 'role_groups')->withPivot('module_id')->withTimestamps();
}
Desired Structre:
The keys are module_id and the values inside are group_id
{
"1": [
1
],
"2": [
1
],
"3": [
1
],
"4": [
1
],
"5": [
1,
2,
3,
4
]
}
What i got:
{
"1": [
{
"role_id": 1,
"module_id": 1,
"group_id": 1
},
{
"role_id": 1,
"module_id": 1,
"group_id": 3
}
],
"2": [
{
"role_id": 1,
"module_id": 2,
"group_id": 1
},
{
"role_id": 1,
"module_id": 2,
"group_id": 3
}
],
"3": [
{
"role_id": 1,
"module_id": 3,
"group_id": 1
}
],
"5": [
{
"role_id": 1,
"module_id": 5,
"group_id": 1
}
]
}
You may achieve this by grouping by multiple columns. i.e:
RoleGroup::query()
->select("module_id", "group_id")
->where("role_id", $role->id)
->groupBy("module_id", "group_id")
->get()
->reduce(
function (array $carry, Model $item) {
$carry[$item->module_id][] = $item->group_id;
return $carry;
}
, []);
References:
reduce()
groupBy()

Laravel parent parameter in the relation

Good afternoon I ran into a problem
database structure
langs
id
title
1
ru
2
en
articles
id
alias
1
test_article_1
2
test_article_2
article_lang
article_id
lang_id
1
1
2
1
1
2
2
2
article_title_lang
article_id
lang_id
title
1
1
novost_1
2
1
novost_2
1
2
article_1
2
2
article_2
models:
Langs
public function articles()
{
return $this->belongsToMany(Articles::class, 'article_lang', 'lang_id', 'article_id');
}
Articles
public function articleTitleLang()
{
// return $this->hasOne(ArticlesTitleLang::class, 'article_id', 'id')->where('lang_id', '=', 1); // i can get titles lang 1
// return $this->hasOne(ArticlesTitleLang::class, 'article_id', 'id')->where('lang_id', '=', 2); // i can get titles lang 2
return $this->hasOne(ArticlesTitleLang::class, 'article_id', 'id'); // always lang id 1 (because the relation has one)
}
in controller:
$articles = Langs::with([
'articles',
'articles.articleTitleLang'
])->get();
return response($articles, 200)
return json:
[
{
"id": 1,
"title": "ru",
"articles": [
{
"id": 1,
"alias": "test_article_1",
"pivot": {
"lang_id": 1,
"article_id": 1
},
"article_title_lang": {
"title": "novost_1",
"article_id": 1,
"lang_id": 1,
}
},
{
"id": 2,
"alias": "test_article_2",
"pivot": {
"lang_id": 1,
"article_id": 2
},
"article_title_lang": {
"title": "novost_1",
"article_id": 2,
"lang_id": 1,
}
}
]
},
{
"id": 2,
"title": "en",
"articles": [
{
"id": 1,
"alias": "test_article_1",
"pivot": {
"lang_id": 2,
"article_id": 1
},
"article_title_lang": {
"title": "novost_1",
"article_id": 1,
"lang_id": 1,
}
},
{
"id": 2,
"alias": "test_article_2",
"pivot": {
"lang_id": 2,
"article_id": 2
},
"article_title_lang": {
"title": "novost_2",
"article_id": 2,
"lang_id": 1,
}
}
]
}
]
I need to change the dynamic value in the Articles model depending on the parent language value, so that when the language value is "en", the result is "title" = article_1, article_2
you can try this, in Articles Model
public function articleTitleLang()
{
return $this->hasMany(ArticlesTitleLang::class, 'article_id', 'id')
->where('lang_id', $this->lang_id)->first();
}

Laravel's Eloquent pivot object

I have defined relation in my model:
public function positions() {
return $this->belongsToMany('Position', 'users_positions')->withPivot('season');
}
Is it possible to display objects with pivot in cleaner way?
For Example:
"positions": [
{
"id": 1,
"name": "A",
"pivot": {
"user_id": 1,
"position_id": 1,
"season": 2014
}
}
],
I would like to get:
"positions": [
{
"id": 1,
"name": "A",
"season": 2014
}
],
Use accessor:
public function getSeasonAttribute()
{
return ($this->pivot) ? $this->pivot->season : null;
}
Then you can access it just like other properties:
$position = $someModel->positions->first();
$position->season;
if you also need it in the toArray/toJson output, then use on you model:
protected $appends = ['season'];

Resources