php artisan goat:make ProductYou paste a schema. GOAT ships the whole slice โ not just a controller.
|
You give Schema::create('products', function($t){
$t->id();
$t->foreignId('category_id')->constrained();
$t->string('name');
$t->decimal('price',10,2);
$t->timestamps();
});or a plain ERD: |
You get โ template-ready Service โ Repository, Resource, Policy, Tests included. |
No AI. No SaaS. No web UI. Pure PHP โ runs 100% locally.
| CRUD generator | GOAT โ Feature / Template / Boilerplate |
|---|---|
| Model + Controller | + Service + Repository (separation) |
| No validation | + Store/Update Requests with inferred rules |
| No API layer | + JsonResource |
| No auth | + Policy (7 methods) |
| No tests | + Feature tests (5 scenarios) |
| One table = one file | ERD with N tables โ N slices |
| Hardcoded paths | All paths & namespaces configurable |
| Fixed stubs | Publish & customize โ vendor:publish --tag=goat-stubs |
Use it as:
- CRUD scaffold for admin panels
- Feature slice for clean architecture (Service/Repo)
- Boilerplate / template for new domains (
Inventory,Order,Booking) - Rapid prototyping from an ERD whiteboard
composer require meehh/laravel-goat --dev
php artisan goat:make Product๐ GOAT โ Laravel Feature Generator
How do you want to define Product?
โฏ Paste Migration
Paste ERD
Use Existing Migration
Paste your migration. Press Ctrl+D when finished:
> Schema::create('products', ...);
> ^D
โ Schema detected
Product (products)
โโโ id bigInteger (PK)
โโโ category_id foreignId โ categories.id
โโโ name string
โโโ price decimal
โโโ created_at timestamp
Generate:
โ Model โ Migration โ Requests โ Resource โ Controller โ Service โ Repository โ Policy โ Tests
โ app/Models/Product.php
โ app/Services/ProductService.php
...
๐ GOAT generated Product successfully!
CI / non-interactive
cat migration.php | php artisan goat:make Product --from=migration --force
cat erd.txt | php artisan goat:make Product --from=erd --force
php artisan goat:make Product --from=database/migrations/2024_01_01_create_products_table.php
php artisan goat:make Product --only=model,resource,request
php artisan goat:make Product --except=policy,test
# custom paths โ e.g. repo/admin instead of repositories/
php artisan goat:make Hello --paths=repository=app/Repo/Admin,model=app/Domain/Models --force
# โ app/Repo/Admin/HelloRepository.php (App\Repo\Admin) + app/Domain/Models/Hello.php
php artisan goat:make World --module=Admin --force
# โ app/Modules/Admin/Models/World.php + app/Modules/Admin/Repositories/...
# interactive: after schema preview GOAT asks โCustomize output paths?โ โ type per componentNo more locked app/Repositories. Put any artifact anywhere โ per run, no config edit needed:
# single: repo/Admin instead of repositories/
php artisan goat:make Hello --paths=repository=app/Repo/Admin --force
# โ app/Repo/Admin/HelloRepository.php (namespace App\Repo\Admin auto)
# multiple: split models & repos
php artisan goat:make Hello --paths=repository=app/Repo/Admin,model=app/Domain/Models --force
# โ app/Domain/Models/Hello.php (App\Domain\Models)
# module: group everything under app/Modules/{Module}
php artisan goat:make World --module=Admin --force
# โ app/Modules/Admin/Models/World.php
# app/Modules/Admin/Repositories/WorldRepository.php
# app/Modules/Admin/Services/WorldService.php
# tests/Feature/Modules/Admin/WorldTest.php
# interactive: no flags โ after schema GOAT asks โCustomize output paths?โ โ y โ type per component
php artisan goat:make Product--paths is key=path comma-separated (model,migration,request,resource,controller,service,repository,policy,test). Paths can be absolute or app/... relative โ namespaces inferred automatically (app/Repo/Admin โ App\Repo\Admin). For permanent change, publish config/goat.php.
Migration โ understands Blueprint:
id, string, text, integer, bigInteger, decimal, float, boolean, date, datetime, timestamp, foreignId, uuid, json, enum, softDeletes, timestamps, unique, nullable, default, constrained, indexโฆ
ERD โ plain text, forgiving:
inventories
------------
id bigint PK
product_name varchar
sku varchar UNIQUE
quantity integer
price decimal(10,2)
created_at timestamp
No type? Inferred (quantityโinteger, priceโdecimal, descriptionโtext). Handles PK, FK -> categories.id, UNIQUE, DEFAULT 0, varchar(100).
ERD full example
products
---------
id bigint PK
category_id bigint FK -> categories.id
name varchar
price decimal(10,2)
categories
----------
id bigint PK
name varchar
GoatSchema is the single source of truth โ parsers feed it, 9 generators consume it.
Product.php โ fillable, casts, belongsTo(Category::class)
protected $fillable = ['category_id','name','price'];
protected function casts(): array { return ['price' => 'decimal:2']; }
public function category(){ return $this->belongsTo(Category::class,'category_id'); }Requests โ required/sometimes + string/integer/numeric/exists:categories,id/unique + max:255
Controller โ thin index/store/show/update/destroy, delegates to ProductService, uses ProductResource
Service โ Repository โ business vs query split, paginate/all/find/create/update/delete
Policy โ viewAny/view/create/update/delete/restore/forceDelete (edit stub to fit)
Test โ test_can_list_products โฆ test_can_delete_product with factory payloads
// config/goat.php
return [
'paths' => [
'model' => app_path('Models'),
'migration' => database_path('migrations'),
'request' => app_path('Http/Requests'),
'resource' => app_path('Http/Resources'),
'controller' => app_path('Http/Controllers'),
'service' => app_path('Services'),
'repository' => app_path('Repositories'),
'policy' => app_path('Policies'),
'test' => base_path('tests/Feature'),
],
'namespaces' => ['model' => 'App\\Models', /* ... */],
'generate' => ['model'=>true, /* ... */],
];Stubs โ one per artifact:
stubs/model.stub, migration.stub, request.stub, resource.stub,
controller.stub, service.stub, repository.stub, policy.stub, test.stub
php artisan vendor:publish --tag=goat-stubs
# โ resources/stubs/vendor/goat/*.stub โ edit once, generate foreverStubRenderer prefers your published stub, falls back to package default.
- Never overwrites without
--forceโโ Product.php already exists. Use --force - Validates
--only/--except, empty input, unsupported types, bad table names โ no stack trace dumps - Naming via
NameResolver(productsโProduct,product_itemsโProductItem,category_idโcategory) --only=model,resource --except=policycomposable
src/
โโโ Commands/MakeCommand.php # UX, STDIN, --from/--only/--except/--force
โโโ Parsers/MigrationParser.php # Blueprint โ GoatSchema
โ ErdParser.php # ERD โ GoatSchema (infer types)
โโโ Schema/GoatSchema|Table|Column|Relationship # โ single source
โโโ Generators/* (9) # Model/Migration/Request/Resource/Controller/Service/Repository/Policy/Test
โโโ Support/NameResolver|StubRenderer|FileWriter|GoatConfig
โโโ GoatServiceProvider.php # config + publish + command
No giant generator. Each artifact isolated, stub-driven, testable.
composer install
composer test # vendor/bin/phpunit โ 40 testsCovers: migration/ERD parsing, columns, relationships, naming, 9 generators, CLI, --only/--except/--force, file protection, stubs.
composer config repositories.goat vcs https://github.com/CodeWithTeds/meehh.git
composer require meehh/laravel-goat:@dev --devOnce on Packagist: composer require meehh/laravel-goat --dev
Requires PHP ^8.3 ยท Laravel 11|12|13
--api/--webpresets, enum casts, factories,--allfor multi-table ERD,goat:make --from=openapi
PRs welcome. Build your next feature with php artisan goat:make.
Built for builders who ship features, not boilerplate.
MIT ยท Owned by Prof Alex Software Dev / TE-AD ยท github.com/CodeWithTeds/meehh ยท Report issue ยท php artisan goat:make
