avatarShaun Thornburgh

Summary

This article explains how to use Laravel's Eloquent ORM to manage parent-child recursive relationships, using the example of category data.

Abstract

In Laravel, a popular PHP framework, recursive relationships are common in many applications. This article demonstrates how to create a categories table, define Eloquent relationships in the Category model, and retrieve data using the parent-child recursive relationship. The article also covers testing the relationships using feature and unit tests in Laravel.

Opinions

  • The author believes that Laravel's Eloquent ORM provides an elegant way of managing parent-child recursive relationships.
  • The author suggests that the concept of parent-child recursive relationships can be applied to any scenario that requires a hierarchical data structure, such as organizational charts or file systems.
  • The author recommends testing other parts of the application, such as validation, authorization, and error handling, in addition to basic CRUD operations and relationships.
  • The author encourages developers to efficiently structure and navigate hierarchical data in Laravel applications with a strong understanding of this relationship.

Using Parent-Child Recursive Relationships in Laravel

Recursive relationships, especially in the context of data like category hierarchies, are quite common in many applications. Laravel, a popular PHP framework, offers robust support for managing these kinds of relationships. This article will explore how to utilize a parent-child recursive relationship in Laravel using the example of category data.

What is a Parent-Child Recursive Relationship?

In databases, a parent-child recursive relationship occurs when a record in a table is related to another record in the same table. In the context of categories, this means a category can have a parent that’s also a category. This creates a hierarchical structure, making it possible to have categories, sub-categories, sub-sub-categories, and so on.

Setting Up The Database

To demonstrate, we’ll create a `categories` table:

Schema::create('categories', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('parent_id')->nullable();
    $table->string('name');
    $table->timestamps();

    $table->foreign('parent_id')
        ->references('id')
        ->on('categories')
         ->onDelete('cascade');
});

Here, the parent_id column refers to the parent category. If it’s null, the category is a top-level category.

Defining The Eloquent Relationship

Next, we’ll define the Eloquent relationships in our Category model:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Category extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'parent_id'
    ];

    public function parent()
    {
        return $this->belongsTo(Category::class, 'parent_id');
    }

    public function children()
    {
        return $this->hasMany(Category::class, 'parent_id');
    }

    public function descendants()
    {
        return $this->children()->with('descendants');
    }
}

- parent() relationship gives the immediate parent category. - children() provides the direct subcategories. - descendants() gives all the child categories recursively.

Retrieving Data

To retrieve the immediate children:

$category = Category::find(1); $children = $category->children;

To retrieve all descendants recursively:

$descendants = $category->descendants;

Using The Relationship in a Real-World Scenario

Imagine you are building an online store and you need to categorise products. The categories can be something like:

- Electronics
 - Mobile Phones
 - Smartphones
 - Feature Phones
 - Laptops
 - Gaming
 - Business
- Clothing
 - Men
 - Women

Each category can have products. Using the parent-child recursive relationship, you can efficiently navigate and manage these product categories.

Testing

Testing is a critical aspect of the development process, especially in a framework like Laravel that encourages a test-driven development approach.

Feature Test: Category Management

Feature tests in Laravel are meant to test a chunk of your application to ensure it works as expected. Here, we’ll test if we can create, read, and relate categories:

1. Set up the test environment:

First, ensure you’ve set up your Laravel testing environment properly. You should be able to use the `RefreshDatabase` trait, which will allow your tests to interact with a fresh database every time.

2. Create a factory:

<?php

namespace Database\Factories;

use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;

class CategoryFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->name,
            'parent_id' => null
        ];
    }
}

3. Create a test:

Run: php artisan make:test CategoryTest

4. Implement the test:

In tests/Feature/CategoryTest.php:

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\Category;
use Illuminate\Foundation\Testing\RefreshDatabase;

class CategoryTest extends TestCase
{
    use RefreshDatabase;

    public function test_a_category_can_be_created()
    {
        $category = Category::factory()->create([
            'name' => 'Electronics'
        ]);
 
        $this->assertDatabaseHas('categories', ['name' => 'Electronics']);
    }

    public function test_a_category_can_have_a_parent_category()
    {
        $parent = Category::factory()->create([
            'name' => 'Electronics'
        ]);

        $child = Category::factory()->create([
            'name' => 'Mobile Phones', 
            'parent_id' => $parent->id
        ]);
        
        $this->assertEquals($parent->id, $child->parent->id);
    }
}

Unit Test: Eloquent Relationships

Unit tests in Laravel generally test the smallest parts of your application in isolation (e.g., methods). Here, we will test the parent-child relationship.

1. Create the test:

Run: php artisan make:test CategoryRelationshipTest --unit

2. Implement the test:

In tests/Unit/CategoryRelationshipTest.php:

namespace Tests\Unit;

use Tests\TestCase;
use App\Models\Category;
use Illuminate\Foundation\Testing\RefreshDatabase;

class CategoryRelationshipTest extends TestCase
{
    use RefreshDatabase;

    public function test_a_category_has_children()
    {
        $category = Category::factory()->create([
            'name' => 'Electronics'
        ]);

        $child1 = Category::factory()->create([
            'name' => 'Mobile Phones', 
            'parent_id' => $category->id
        ]);

        $child2 = Category::factory()->create([
            'name' => 'Laptops', 
            'parent_id' => $category->id
        ]);

        $this->assertCount(2, $category->children);
    }

    public function test_a_category_has_a_parent()
    {
        $parent = Category::factory()->create([
            'name' => 'Electronics'
        ]);

        $child = Category::factory()->create([
            'name' => 'Mobile Phones', 
            'parent_id' => $parent->id
        ]);

        $this->assertEquals($parent->id, $child->parent->id);
    }
}

Finally, to run your tests: php artisan test

You should see the following (note I am using sail so my commend to run the tests is sail artisan test ).

These tests cover basic CRUD operations and relationships. In a real-world scenario, you’d also want to test other parts of your application, such as validation, authorisation, and error handling.

Conclusion

Laravel’s Eloquent ORM provides an elegant way of managing parent-child recursive relationships. While our example focused on categories, the concept can be applied to any scenario that requires a hierarchical data structure, like organisational charts or file systems. With a strong understanding of this relationship, developers can efficiently structure and navigate hierarchical data in Laravel applications.

Resources

Feel free to check out my code here: https://github.com/shaunthornburgh/parent-child-recursive-demo

Laravel
PHP
Recommended from ReadMedium