Get tree by recursion - laravel

I have a flat array with each element containing an 'InstitutionId' and a 'ParentInstitutionId'. Each element will only have ONE parent, but may have multiple children. I know it need recursive solution, I am stuck and can't find way out.
Array
(
[0] => Array
(
[InstitutionId] => 17507
[InstitutionName] => abc
[ParentInstitutionId] => 60936
)
[1] => Array
(
[InstitutionId] => 41679
[InstitutionName] => abc
[ParentInstitutionId] => 55701
)
[2] => Array
(
[InstitutionId] => 55701
[InstitutionName] => abc
[ParentInstitutionId] =>
)
[3] => Array
(
[InstitutionId] => 60936
[InstitutionName] => abc
[ParentInstitutionId] => 128629
)
[4] => Array
(
[InstitutionId] => 71737
[InstitutionName] => abc
[ParentInstitutionId] => 17507
)
)
How can I make this a tree, children and there children should come under one array starting from root.

You can use the hasMany relationship feature to achieve that. Here is one example you can adapt:
1st - Declare your relationship in your model with a significant name of your choosing. Here we say that the Tree model (file that we're currently in) HAS MANY Tree objects (yes, has many itself).
2nd - Declare the recursive tree loading all immediate children.
Tree Model
class Tree extends Model {
public function tree_immediate_children() {
return $this->hasMany(Tree::class);
}
public function recursive_tree(){
return $this->tree_immediate_children()->with('recursive_tree');
}
}
3rd - Eager load the relationship and then filter by absolute parents only. This way you only get the children through the parents.
Returning the Tree with Children
return \App\Tree::with('recursive_tree')->get()->where('tree_id', null);
This is the migration I used to achieve the previous code.
Migration
public function up() {
Schema::create('trees', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
$table->integer('tree_id')->unsigned()->nullable(true);
$table->index(['tree_id']);
$table->foreign('tree_id')->references('id')->on('trees')->onDelete('cascade');
});
}
public function down() {
Schema::drop('trees');
}
Result
{
"trees": [
{
"id": 1,
"name": "Lauriane Denesik",
"created_at": "2016-07-25 13:55:34",
"updated_at": "2016-07-25 13:55:34",
"tree_id": null,
"recursive_tree": [
{
"id": 4,
"name": "Lea Terry",
"created_at": "2016-07-25 13:55:39",
"updated_at": "2016-07-25 13:55:39",
"tree_id": 1,
"recursive_tree": []
},
{
"id": 5,
"name": "Erna Jacobi",
"created_at": "2016-07-25 13:55:39",
"updated_at": "2016-07-25 13:55:39",
"tree_id": 1,
"recursive_tree": []
},
{
"id": 6,
"name": "Carmen Ferry",
"created_at": "2016-07-25 13:55:39",
"updated_at": "2016-07-25 13:55:39",
"tree_id": 1,
"recursive_tree": [
{
"id": 10,
"name": "Alford Yost",
"created_at": "2016-07-25 13:55:44",
"updated_at": "2016-07-25 13:55:44",
"tree_id": 6,
"recursive_tree": []
},
{
"id": 11,
"name": "Eusebio Padberg",
"created_at": "2016-07-25 13:55:44",
"updated_at": "2016-07-25 13:55:44",
"tree_id": 6,
"recursive_tree": []
},
{
"id": 12,
"name": "Abdullah Wunsch",
"created_at": "2016-07-25 13:55:44",
"updated_at": "2016-07-25 13:55:44",
"tree_id": 6,
"recursive_tree": []
}
]
}
]
},
{
"id": 2,
"name": "Cruz Dickens",
"created_at": "2016-07-25 13:55:34",
"updated_at": "2016-07-25 13:55:34",
"tree_id": null,
"recursive_tree": [
{
"id": 7,
"name": "Mr. Jesus Macejkovic DDS",
"created_at": "2016-07-25 13:55:42",
"updated_at": "2016-07-25 13:55:42",
"tree_id": 2,
"recursive_tree": []
},
{
"id": 8,
"name": "Tracy Jacobson PhD",
"created_at": "2016-07-25 13:55:42",
"updated_at": "2016-07-25 13:55:42",
"tree_id": 2,
"recursive_tree": []
},
{
"id": 9,
"name": "Prof. Uriel Goldner",
"created_at": "2016-07-25 13:55:42",
"updated_at": "2016-07-25 13:55:42",
"tree_id": 2,
"recursive_tree": []
}
]
},
{
"id": 3,
"name": "Sabryna Torp",
"created_at": "2016-07-25 13:55:34",
"updated_at": "2016-07-25 13:55:34",
"tree_id": null,
"recursive_tree": []
}
]
}

Since your tags include Laravel, I will give you a laravel solution:
$collection = collect($yourArray)->keyBy('InstitutionId');
$grouped = $collection->groupBy('ParentInstitutionId')
->map(function($children){
return $children->keyBy('InstitutionId');
})->all();
$array = $collection->all();
foreach ($array as $id => $item ){
if (isset($grouped[$id]))
$array[$id]['children'] = $grouped[$id];
}
foreach ($array as $id => $item ){
if ($item['ParentInstitutionId']) {
$parentId = $item['ParentInstitutionId'];
$array[$parentId]['children'][$id] = $item;
}
}
$tree = array_shift($array);

Related

How do I serach in laravel Eloquent relationships?

I have a function that returns data as required. When I add a search string so that I can filter the results, no filtering is done.
Function
public function getComboSubItemRequirements(Request $request, $comboId){
$search_string = $request->query('search_string');
$search_string = empty($search_string)
? null
: trim(urldecode($search_string));
$combo = ComboSellable::where('id', $comboId)
->with('sellable')
->with('items')
->first();
$sellable_id = $combo->sellable->sellable_id;
$sellables = Sellable::thisMerchant()
->with('sellable')
->where('sellable_type', '<>', Sellable::COMBO)
->when(!empty($sellable_id) && !is_numeric($sellable_id), function ($q) use($search_string){
$q->whereHasMorph(
'sellable',
[Sellable::SERVICE, Sellable::PRODUCT],
function ($qq) use ($search_string){
return $qq->where('sellable.name', 'like', "%{$search_string}%");
}
);
})
->whereNotIn('id', $combo->items->pluck('sellable_id')->toArray())
->limit(10)
->get();
return response()->json($sellables);
}
}
Result structure
[
{
"id": 3,
"merchant_id": 2,
"sellable_id": 3,
"sellable_type": "App\\Product",
"created_at": null,
"updated_at": null,
"deleted_at": null,
"sellable": {
"id": 3,
"merchant_id": "2",
"name": "Wash and set weave",
"description": "",
"cost": "1000",
"image": null,
"duration": "60",
"remainder_days": null,
"created_at": "2020-12-02T06:59:24.000000Z",
"updated_at": "2021-06-19T16:03:30.000000Z",
"commission": null,
"deleted_at": null,
"can_be_sold": 1,
"tax_id": null,
"tax_method": null
}
},
{
"id": 4,
"merchant_id": 2,
"sellable_id": 4,
"sellable_type": "App\\Product",
"created_at": null,
"updated_at": null,
"deleted_at": null,
"sellable": {
"id": 4,
"merchant_id": "2",
"name": "Wash andset tracks",
"description": "",
"cost": "800",
"image": null,
"duration": "75",
"remainder_days": null,
"created_at": "2020-12-02T06:59:53.000000Z",
"updated_at": "2021-08-21T02:47:18.000000Z",
"commission": null,
"deleted_at": null,
"can_be_sold": 1,
"tax_id": null,
"tax_method": null
}
}
]
I want to search items based on sellable->name. I have added the sellable table to the query but the results are still the same. No search or filtering is done.
How do I filter in such a case?

How to loop read all the column from the spreadsheet using Laravel-Excel

I have a spreadsheet that contains quiz records where I save all the quiz_questions and choices in the database.
I have this code reading and saving the spreadsheet but it has some issues, I want to save the choice_1, choice_2, choice_3, and choice_4 inside the database with the column name of choices. When I run this code it only save the choice_4 columns but not all of the choices. How can I get all the choices and save it? can anyone help me? thanks in advance.
This is my QuizImport
public function collection(Collection $rows)
{
foreach($rows as $row){
$quizQuestions = quizQuestions::create([
'quiz_id' => $row['quiz_id'],
'question_num' => $row['question_num'],
'question_content' => $row['question_content'],
'tagalog_question'=> $row['tagalog_question'],
]);
$quizQuestions->choices()->create([
'option' => $row['option_1'] || $row['option_2'] || $row['option_3'] || $row['option_4'],
'remarks' => $row['remarks_1'] || $row['remarks_2'] || $row['remarks_3'] || $row['remarks_4'],
'tagalog_choices'=> $row['tagalog_choices'],
]);
}
}
This is my controller
public function store(Request $request){
$file = $request->file('file');
Excel::import(new QuizImport, $file);
return response(['message'=>"Quiz successfully imported",
'error'=>false,
'error code'=>200,
'line'=>"line".__LINE__."".basename(__LINE__),
'file'=>$file],200,[],JSON_NUMERIC_CHECK);
}
This is the sample structure of my excel
This is the expected output
"quizQuestions": [
{
"id": 2,
"quiz_id": 6,
"question_num": 1,
"question_content": "<p>Sample Question</p>",
"correct_answer": null,
"created_by": 7,
"updated_by": null,
"created_at": "2021-09-06T07:27:20.000000Z",
"updated_at": "2021-10-18T17:35:17.000000Z",
"question_image": null,
"tagalog_question": "<p><span style=\"color: black;\">Tagalog Translation of the question</span></p>",
"choices": [
{
"id": 2,
"quiz_questions_id": 2,
"option": "choices_1",
"remarks": 1,
"created_at": "2021-09-06T07:27:48.000000Z",
"updated_at": "2021-10-18T17:53:13.000000Z",
"choices_image": null,
"tagalog_choices": "Tagalog"
},
{
"id": 3,
"quiz_questions_id": 2,
"option": "choices_2",
"remarks": 0,
"created_at": "2021-09-06T07:28:01.000000Z",
"updated_at": "2021-10-18T17:53:25.000000Z",
"choices_image": null,
"tagalog_choices": "Tagalog"
},
{
"id": 4,
"quiz_questions_id": 2,
"option": "choices_3",
"remarks": 0,
"created_at": "2021-09-06T07:28:54.000000Z",
"updated_at": "2021-10-18T17:53:32.000000Z",
"choices_image": null,
"tagalog_choices": "Tagalog"
},
{
"id": 5,
"quiz_questions_id": 2,
"option": "choices_4",
"remarks": 0,
"created_at": "2021-09-06T07:29:07.000000Z",
"updated_at": "2021-10-18T17:53:37.000000Z",
"choices_image": null,
"tagalog_choices": "Tagalog"
}
]
},
But this is what I get
"quizQuestions": [
{
"id": 541,
"quiz_id": 33,
"question_num": 1,
"question_content": "Sample Question",
"correct_answer": null,
"created_by": null,
"updated_by": null,
"created_at": "2022-01-10T11:35:42.000000Z",
"updated_at": "2022-01-10T11:35:42.000000Z",
"question_image": null,
"tagalog_question": "Tagalog Translation of the Question",
"choices": [
{
"id": 1735,
"quiz_questions_id": 541,
"option": "choice4",
"remarks": 0,
"created_at": "2022-01-10T11:35:42.000000Z",
"updated_at": "2022-01-10T11:35:42.000000Z",
"choices_image": null,
"tagalog_choices": "Tagalog"
}
]
},
I believe you need a loop for creating multiple choices with the same parent. (not tested)
public function collection(Collection $rows)
{
foreach($rows as $row){
$quizQuestions = quizQuestions::create([
'quiz_id' => $row['quiz_id'],
'question_num' => $row['question_num'],
'question_content' => $row['question_content'],
'tagalog_question'=> $row['tagalog_question'],
]);
for($i = 1; $i < 5; $++) {
$quizQuestions->choices()->create([
'option' => $row['option_' .$i],
'remarks' => $row['remarks_' . $i],
'tagalog_choices'=> $row['tagalog_choices'],
]);
}
}
}

Show just the name of collection?

I have pivot table post_tag between posts and tags table.
I want to show the tags name for each post in resource API.
Resource file code snippets:
public function toArray($request)
{
return [
'Name'=> $this->title,
'Description'=> $this->desc,
'Image'=> $this->image_path,
'Posted By'=> $this->user->name,
'Category Name'=> $this->category->name,
'Tags' => $this->tags,
];
}
Relation added in Post model:
public function tags()
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
One of Results of an API:
"Name": "first post",
"Description": "desc of the post",
"Image": "https://blog.test/uploads/post/SjvDfC1Zk5UzGO6ViRbjUQTMocwCh0lzEYW1Gufp.jpeg",
"Posted By": "test",
"Category Name": "web dev",
"Tags": [
{
"id": 1,
"name": "HTML",
"created_at": "2020-01-08 19:19:55",
"updated_at": "2020-01-08 19:19:55",
"pivot": {
"post_id": 1,
"tag_id": 1,
"created_at": "2020-01-08 19:21:40",
"updated_at": "2020-01-08 19:21:40"
}
},
{
"id": 2,
"name": "CSS",
"created_at": "2020-01-08 19:19:55",
"updated_at": "2020-01-08 19:19:55",
"pivot": {
"post_id": 1,
"tag_id": 2,
"created_at": "2020-01-08 19:21:40",
"updated_at": "2020-01-08 19:21:40"
}
},
I need to show like that JUST names:
"Tags" : {
'HTML',
'CSS',
'JS',
}
You must be use like this
return [
'Name'=> $this->title,
'Description'=> $this->desc,
'Image'=> $this->image_path,
'Posted By'=> $this->user->name,
'Category Name'=> $this->category->name,
'Tags' => $this->tags->pluck('name')
];

How to map Laravel API resources for Non-Related Models

Hi I'm creating non related model resources. Inventories and Categories. They have child relationship models but they are not connected directly.
Sample:
{
"data": {
"inventories": [
{
"id": 1,
"user_id": 1,
"sub_category_id": 6,
"created_at": "2019-03-08 03:00:00",
"updated_at": "2019-03-08 03:00:00",
"deleted_at": null,
"get_inventory_details": {
"id": 1,
"inventory_id": 1,
"image_path": "Pahiram-Drone1-1.png",
"name": "Drone i1",
"description": "Drone i1 The Best Drone yet!",
"quantity": 10,
"cost_per_day": 1000,
"cost_per_hour": 100,
"status": "0",
"created_at": "2019-03-08 03:00:00",
"updated_at": "2019-03-08 03:00:00",
"deleted_at": null
}
}
],"categories": [
{
"id": 1,
"category_id": 1,
"parent_id": null,
"created_at": "2019-03-08 03:00:00",
"updated_at": "2019-03-08 03:00:00",
"deleted_at": null,
"get_sub_category_details": {
"id": 1,
"sub_category_id": 1,
"category_type_id": 1,
"name": "Drone DJI 1",
"description": "Drone DJI 1",
"created_at": "2019-03-08 03:34:23",
"updated_at": "2019-03-08 03:34:23",
"deleted_at": null
}
}
]
}
}
This is my sample data
It works with:
public function toArray($request)
{
return parent::toArray($request);
}
But not:
public function toArray($request)
{
return [
'inventory_ids' => $this->inventories->id,
'category_ids' => $this->categories->id
];
}
My expected result is to be able to map my response.
You Don't access direct id from categories and inventories, because its an array not object.
This should be usefull for you.
$categoryIds = Collection::make($this->categories)->pluck('id')->toArray();
$inventoryIds = Collection::make($this->inventories)->pluck('id')->toArray();
return [
'category_ids' => $categoryIds,
'inventory_ids' => $inventoryIds
];

How to change the response in API

I want to change my response in an API. I get the user object in response but i want there the object of join like that here is the code of my Activity Profile Controller in which I get user object.
Currently i get this response.
"user": {
"id": 1,
"facebook_id": null,
"name": "umar",
"surname": "javed",
"email": "uj1#gmail.com",
"username": null,
"date_of_birth": "1993-01-18",
"profile_picture": null,
"occupation": null,
"gender": null,
"city": "lahore",
"country": null,
"course": null,
"university": null,
"discription": null,
"visible_on_feeds": 1,
"visible_on_guestlists": 1,
"created_at": "2018-05-02 10:35:03",
"updated_at": "2019-01-15 14:32:54"
}
public function joinActivity(Activity $activity)
{
$authUser = JWTAuth::parseToken()->toUser();
$userAge = Carbon::parse($authUser->date_of_birth)->age;
if($activity->age_from <= $userAge && $activity->age_to >= $userAge)
{
$authUser->joinActivity($activity, $authUser);
if ($activity->user_id != $authUser->id) {
$user = User::where('id', $activity->user_id)->first();
$user->notify(new JoinedTheActivity($authUser, $activity));
}
$userFriendIds = $authUser->friendslist()->pluck('id')->toArray();
$joinedIds = $activity->joins()->pluck('user_id')->toArray();
$joinedUsers = User::whereIn('id', $joinedIds)->whereIn('id', $userFriendIds)
->where('id', '!=', $authUser->id)->get();
foreach ($joinedUsers as $joinedUser) {
$joinedUser->notify(new FriendJoinedSameActivity($authUser, $activity));
}
return response()->json(['user'=> $authUser], 200);
} else {
return response()->json(['message'=> 'Your are not under the age limit of this Activity']);
}
}
And Here is the code of my ActivityInterest trait:
public function joinActivity(Activity $activity, User $user)
{
if ( $this->isJoiningActivity($activity, $user))
{
$activity->interests()->update(['status'=> Status::JOINED]);
} else {
$joining = $activity->interests()->create([
'user_id' => $user->id,
'activity_id' => $activity->id,
'status' => Status::JOINED,
]);
return $joining->save();
}
}
But I want this response How i can do that Please help Me. Thanks in advance.
"joins": [
{
"id": 71,
"user_id": 1,
"activity_id": 7,
"status": 1,
"created_at": "2018-12-21 07:05:30",
"updated_at": "2018-12-21 07:05:30",
"user": {
"id": 1,
"facebook_id": null,
"name": "umar",
"surname": "javed",
"email": "uj1#gmail.com",
"username": null,
"date_of_birth": "1993-01-18",
"profile_picture": null,
"occupation": null,
"gender": null,
"city": "lahore",
"country": null,
"course": null,
"university": null,
"discription": null,
"visible_on_feeds": 1,
"visible_on_guestlists": 1,
"created_at": "2018-05-02 10:35:03",
"updated_at": "2019-01-15 14:32:54"
},
"activity": {
"id": 7,
"activity_type": "event",
"user_id": 1,
"category_id": 2,
"subcategory_id": 3,
"activity_privacy": "open",
"activity_privacy_visible": 1,
"activity_datetime_from": "2018-07-28 13:35:00",
"activity_datetime_to": "2018-12-12 04:10:12",
"activity_address": "lahore",
"company_id": 3,
"latitude": null,
"longitude": null,
"age_from": 16,
"age_to": 30,
"people_limit": "4",
"activity_picture": null,
"activity_title": "party",
"activity_description": "party bla bla",
"created_at": "2018-05-03 10:49:48",
"updated_at": "2018-09-29 07:57:10",
"user": {
"id": 1,
"facebook_id": null,
"name": "umar",
"surname": "javed",
"email": "uj1#gmail.com",
"username": null,
"date_of_birth": "1993-01-18",
"profile_picture": null,
"occupation": null,
"gender": null,
"city": "lahore",
"country": null,
"course": null,
"university": null,
"discription": null,
"visible_on_feeds": 1,
"visible_on_guestlists": 1,
"created_at": "2018-05-02 10:35:03",
"updated_at": "2019-01-15 14:32:54"
},
"subcategory": {
"id": 3,
"category_id": 2,
"subcategory_name": "party",
"subcategory_picture": "dds",
"created_at": null,
"updated_at": null,
"category": {
"id": 2,
"category_name": "nightlife",
"category_picture": "knk",
"category_color": "#992233",
"created_at": null,
"updated_at": "2018-08-09 11:21:23"
}
}
}
}
]
When you need to change a response in your api, you can create a resource. this is the correct way, and will help you returning the information the way you need it.
In example, php artisan make:resource UserCollection
More info here:
https://laravel.com/docs/5.7/eloquent-resources

Resources