©Sergey Emelyanov 2025 | Alle Rechte vorbehalten
In vielen Onlineshops werden Produkte in Kategorien und Unterkategorien gruppiert. Die Verschachtelung dieser Kategorien kann dabei beliebig tief gehen. In diesem Artikel zeigen wir, wie Sie mit Laravel Eloquent einen Kategorienbaum aufbauen und diesen im JSON-Format über eine API bereitstellen können. Dabei kommen Migrations, das Eloquent-Modell, Form Requests, Routen, Controller und ein Transformer zum Einsatz.
Zunächst legen wir eine Migration an, um eine einfache Tabelle für die Kategorien zu erstellen. Das Beispiel umfasst zwei wesentliche Felder – Name und Beschreibung – sowie ein Feld parent_id, das die hierarchische Beziehung abbildet. Dabei erhalten übergeordnete Kategorien den Wert NULL, während untergeordnete Kategorien auf die ID ihrer Elternkategorie verweisen.
Beispielmigration:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateListingCategoriesTable extends Migration
{
public function up()
{
Schema::create('listing_categories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->longText('description')->nullable();
$table->integer('parent_id')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
public function down()
{
Schema::dropIfExists('listing_categories');
}
}
Im nächsten Schritt erstellen wir das Model app/Models/ListingCategory.php. Hier definieren wir neben den Standard-Feldern auch zwei Methoden, um die Kinderkategorien abzurufen.
Beispielmodell:
namespace App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
final class ListingCategory extends Model
{
public const RESOURCE_NAME = 'listing_categories';
public const DOMAIN_NAME = 'Listings';
protected $table = 'listing_categories';
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
];
protected $fillable = [
'name',
'description',
'parent_id',
'created_at',
'updated_at',
'deleted_at',
];
protected function serializeDate(DateTimeInterface $date): string
{
return $date->format('Y-m-d H:i:s');
}
// Direkte Beziehung zu Kindkategorien
public function childs(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(__CLASS__, 'parent_id', 'id');
}
// Rekursive Beziehung: lädt alle verschachtelten Kindkategorien
public function childrenCategories(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(__CLASS__, 'parent_id', 'id')->with('childs');
}
}
Um die Validierung beim Anlegen einer neuen Kategorie zu vereinfachen, nutzen wir einen Form Request, der nach dem JSON:API Standard konfiguriert ist. Dabei werden Felder wie name, description und parent_id geprüft.
Beispiel eines Form Requests:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
final class StoreListingCategoryRequest extends FormRequest
{
public function authorize(): bool
{
return $this->check('listing_category_create');
}
public function rules(): array
{
$rules = [
'data.attributes.name' => [
'string',
'min:3',
'max:190',
'nullable',
],
'data.attributes.description' => [
'string',
'nullable',
],
'data.attributes.parent_id' => [
'integer',
'min:-2147483648',
'max:2147483647',
'nullable',
'exists:listing_categories,id'
]
];
return $this->mergeWithDefaultRules($rules);
}
}
Um den Kategorienbaum abzurufen, definieren wir eine API-Route in der Datei api.php. Diese Route verweist auf einen Controller, der den Baum liefert. Im Beispiel wird die Bibliothek Fractal eingesetzt, um die Daten in ein JSON:API Format zu transformieren.
Route:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\V1\ListingCategoriesApiController;
Route::get('listing-categories/tree', [
ListingCategoriesApiController::class,
'tree'
])->name('listing-categories.tree');
Im Controller implementieren wir die Methode tree, die mithilfe eines Actions-Klassen (z. B. GetCategoryTreeAction) den Kategorienbaum erstellt. Als Datenbasis laden wir alle Root-Kategorien (wo parent_id NULL ist) und laden rekursiv die Kindkategorien:
public function tree(IndexListingCategoriesRequest $request, GetCategoryTreeAction $action): ?JsonResponse
{
return fractal(
$action(),
new TreeCategoryTransformer(),
new \League\Fractal\Serializer\JsonApiSerializer($this->getUrl())
)
->withResourceName(ListingCategory::RESOURCE_NAME)
->respondJsonApi();
}
Die Action kann beispielsweise so aussehen:
ListingCategory::whereNull('parent_id')
->with('childrenCategories')
->get();
Der Transformer formatiert die Daten in die gewünschte Struktur. Er wandelt jede Kategorie in ein Array um und fügt, falls vorhanden, eine children-Schlüssel hinzu – sodass die verschachtelte Hierarchie abgebildet wird.
Beispiel Transformer:
namespace App\Transformers;
use App\Models\ListingCategory;
use Parents\Transformers\Transformer;
final class TreeCategoryTransformer extends Transformer
{
public function transform(ListingCategory $category): array
{
$item = [
'id' => $category->id,
'name' => $category->name,
'description' => $category->description,
'parent_id' => $category->parent_id,
];
if ($category->childrenCategories && $category->childrenCategories->isNotEmpty()) {
foreach ($category->childrenCategories as $child) {
$item['children'][] = $this->transform($child);
}
}
return $item;
}
}
Nach dem Aufruf der API unter api/v1/listing-categories/tree erhalten Sie einen JSON-Baum, der die Kategorien und ihre verschachtelten Unterkategorien übersichtlich darstellt. Ein mögliches Ergebnis könnte folgendermaßen aussehen:
{
"data": [
{
"type": "listing_categories",
"id": "1",
"attributes": {
"name": "Equipment",
"description": "Super equipment",
"parent_id": null
},
"links": {
"self": "http://example.test/listing_categories/1"
}
},
{
"type": "listing_categories",
"id": "2",
"attributes": {
"name": "Electronics",
"description": "Super electro products",
"parent_id": null,
"children": [
{
"id": 3,
"name": "Computers",
"description": "Super computers and pcs",
"parent_id": 2,
"children": [
{
"id": 4,
"name": "Computer Accessories",
"description": "Keyboard, mouses etc",
"parent_id": 3
},
{
"id": 5,
"name": "Computer Equipment",
"description": "equipment for computers",
"parent_id": 3
}
]
},
{
"id": 6,
"name": "Mobile",
"description": "Mobile phones",
"parent_id": 2
}
]
},
"links": {
"self": "http://example.test/listing_categories/2"
}
}
]
}
Fazit
Mit Laravel Eloquent und einer rekursiven Beziehung im Modell können Sie einen flexiblen und leistungsfähigen Kategorienbaum erstellen und diesen im JSON-Format über eine API bereitstellen. Die Kombination aus Migrations, gut strukturierten Eloquent-Modellen, validierten Form Requests, spezifischen Routen und Controllern sowie einem Transformer (zum Beispiel unter Verwendung von Fractal) ermöglicht es, komplexe, hierarchische Datenstrukturen übersichtlich und performant abzubilden. Dieses Konzept ist ideal für Onlineshops und andere Anwendungen, in denen Produkte oder Daten in Kategorien organisiert werden müssen.
©Sergey Emelyanov 2025 | Alle Rechte vorbehalten