anas/easy-dev
Interactive Laravel code generator for complete CRUD with repository/service patterns. Auto-detects model relationships and scaffolds policies, DTOs, observers, filters, enums, API resources, routes, and more, with dry-run mode and customizable stubs.
Laravel Easy Dev v2 features intelligent relationship detection that analyzes your database schema and automatically generates appropriate Eloquent relationships.
The relationship detection system works by:
Detected From:
_idforeignId() calls in migrationsExamples:
-- Database schema
CREATE TABLE posts (
id BIGINT PRIMARY KEY,
title VARCHAR(255),
user_id BIGINT,
category_id BIGINT,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
);
Generated Code:
// In Post model
public function user()
{
return $this->belongsTo(User::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
Migration Patterns Detected:
// Pattern 1: foreignId with constrained
$table->foreignId('user_id')->constrained();
// Pattern 2: foreignId with explicit table
$table->foreignId('category_id')->constrained('categories');
// Pattern 3: Manual foreign key
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
Detected From:
Examples:
-- Users table
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(255)
);
-- Posts table references users
CREATE TABLE posts (
id BIGINT PRIMARY KEY,
user_id BIGINT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
Generated Code:
// In User model (reverse relationship)
public function posts()
{
return $this->hasMany(Post::class);
}
Detected From:
table1_table2)Examples:
-- Pivot table
CREATE TABLE post_tag (
id BIGINT PRIMARY KEY,
post_id BIGINT,
tag_id BIGINT,
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
Generated Code:
// In Post model
public function tags()
{
return $this->belongsToMany(Tag::class);
}
// In Tag model
public function posts()
{
return $this->belongsToMany(Post::class);
}
Pivot Table Patterns:
// Standard pivot table migration
Schema::create('post_tag', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained();
$table->foreignId('tag_id')->constrained();
$table->timestamps(); // Optional
});
Detected From:
_type and _idmorphs() method in migrationsExamples:
-- Comments table with polymorphic relationship
CREATE TABLE comments (
id BIGINT PRIMARY KEY,
content TEXT,
commentable_type VARCHAR(255),
commentable_id BIGINT,
user_id BIGINT
);
Generated Code:
// In Comment model
public function commentable()
{
return $this->morphTo();
}
Migration Patterns:
// Using morphs helper
$table->morphs('commentable'); // Creates commentable_type and commentable_id
// Manual columns
$table->string('commentable_type');
$table->unsignedBigInteger('commentable_id');
Detected From:
Generated Code:
// In Post model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// In Video model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
Detected From:
parent_idExamples:
-- Categories with parent/child structure
CREATE TABLE categories (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
parent_id BIGINT NULL,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
Generated Code:
// In Category model
public function parent()
{
return $this->belongsTo(Category::class, 'parent_id');
}
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
The system uses multiple detection methods:
Database Schema Analysis (Primary)
// For SQLite
$foreignKeys = DB::select("PRAGMA foreign_key_list({$tableName})");
// For MySQL
$foreignKeys = DB::select("
SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_NAME = ? AND CONSTRAINT_NAME != 'PRIMARY'
", [$tableName]);
Migration File Analysis (Fallback)
// Detect patterns in migration files
preg_match_all('/\$table->foreignId\([\'"]([^\'"]+)[\'"]\)/', $content, $matches);
Identifies pivot tables by:
table1_table2 format// Algorithm pseudocode
function isPivotTable($tableName, $foreignKeys) {
return count($foreignKeys) === 2
&& preg_match('/^(\w+)_(\w+)$/', $tableName)
&& !hasTimestamps($tableName);
}
Looks for polymorphic patterns:
_type_id{name}_type and {name}_id// Detection pattern
function isPolymorphic($columns) {
foreach ($columns as $column) {
if (str_ends_with($column, '_type')) {
$baseName = str_replace('_type', '', $column);
$idColumn = $baseName . '_id';
if (in_array($idColumn, $columns)) {
return $baseName;
}
}
}
return false;
}
# Detect relationships for a specific model
php artisan easy-dev:sync-relations Product
# Detect relationships for all models
php artisan easy-dev:sync-relations --all
-- Users table
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
);
-- Categories table (self-referencing)
CREATE TABLE categories (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
parent_id BIGINT NULL,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
-- Products table
CREATE TABLE products (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
price DECIMAL(10,2),
category_id BIGINT,
user_id BIGINT,
FOREIGN KEY (category_id) REFERENCES categories(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Orders table
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT,
total DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Order items table
CREATE TABLE order_items (
id BIGINT PRIMARY KEY,
order_id BIGINT,
product_id BIGINT,
quantity INTEGER,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- Product tags (many-to-many)
CREATE TABLE product_tag (
product_id BIGINT,
tag_id BIGINT,
FOREIGN KEY (product_id) REFERENCES products(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
-- Reviews (polymorphic)
CREATE TABLE reviews (
id BIGINT PRIMARY KEY,
content TEXT,
rating INTEGER,
reviewable_type VARCHAR(255),
reviewable_id BIGINT,
user_id BIGINT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
Running Detection:
php artisan easy-dev:sync-relations --all
Generated Relationships:
User Model:
public function products()
{
return $this->hasMany(Product::class);
}
public function orders()
{
return $this->hasMany(Order::class);
}
public function reviews()
{
return $this->hasMany(Review::class);
}
Category Model:
public function parent()
{
return $this->belongsTo(Category::class, 'parent_id');
}
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
public function products()
{
return $this->hasMany(Product::class);
}
Product Model:
public function category()
{
return $this->belongsTo(Category::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
public function orderItems()
{
return $this->hasMany(OrderItem::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
public function reviews()
{
return $this->morphMany(Review::class, 'reviewable');
}
Order Model:
public function user()
{
return $this->belongsTo(User::class);
}
public function orderItems()
{
return $this->hasMany(OrderItem::class);
}
Review Model:
public function reviewable()
{
return $this->morphTo();
}
public function user()
{
return $this->belongsTo(User::class);
}
Specify which models can be polymorphic targets:
php artisan easy-dev:sync-relations Comment --morph-targets=Post,Video,Product
You can customize detection in the config file:
// config/easy-dev.php
'database' => [
'relationship_detection' => true,
'foreign_key_detection' => true,
'polymorphic_detection' => true,
'pivot_table_detection' => true,
'self_referencing_detection' => true,
// Custom patterns
'foreign_key_patterns' => [
'/(\w+)_id$/',
'/(\w+)_uuid$/',
],
'polymorphic_patterns' => [
'/(\w+)able_type$/',
'/(\w+)_type$/',
],
],
Relationships Not Detected
Duplicate Relationships
Wrong Relationship Types
Run with verbose output to see detection process:
php artisan easy-dev:sync-relations Product --verbose
If auto-detection doesn't work, add relationships manually:
php artisan easy-dev:add-relation Post belongsTo User
php artisan easy-dev:add-relation User hasMany Post --method=articles
Follow Laravel Conventions
foreignId() in migrations{model}_idProper Migration Structure
// Good
$table->foreignId('user_id')->constrained();
// Also good
$table->foreignId('category_id')->constrained('categories');
// Avoid
$table->integer('userid'); // Non-standard naming
Run Detection After Schema Changes
# After adding new migrations
php artisan migrate
php artisan easy-dev:sync-relations --all
Verify Generated Relationships
The relationship detection system makes it easy to maintain consistent, well-structured Eloquent relationships across your entire application!
How can I help you explore Laravel packages today?