Every relationship method takes optional key arguments, and Eloquent guesses each one you omit. The guess depends on which side of the key the model sits on, and mixing up the rules is the usual reason a relationship returns nothing:
| Relationship | Key guessed from | Shop example |
|---|---|---|
| hasOne, hasMany | declaring class + _id | orders.customer_id |
| belongsTo | method name + _id | products.category_id |
| belongsToMany | both classes, alphabetical | pivot order_product |
The local key is the primary key, id. Category meets both of the first two rules: parent() is a bare belongsTo(Category::class), since the method name yields parent_id, but children() is a hasMany that would guess category_id, so it passes 'parent_id'. Check a guess by compiling the relationship:
use App\Models\{Category, Order, Product};
$programming = Category::find(2);
echo $programming->parent()->toRawSql(), "\n", $programming->children()->toRawSql(), "\n";
echo $programming->hasMany(Category::class)->getForeignKeyName(), "\n";
echo Order::find(1)->belongsToMany(Product::class)->getTable(), "\n";select * from `categories` where `categories`.`id` = 1
select * from `categories` where `categories`.`parent_id` = 2 and `categories`.`parent_id` is
not null
category_id
order_productThe last two lines are the defaults you would have got: a missing column and a pivot table that is really called order_items. belongsTo binds the child's own parent_id (1); hasMany binds the parent's id and skips rows whose key is NULL.