dgtlss / routa
Automatically generate resource route URLs on Laravel models using a trait.
Installs: 15
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/dgtlss/routa
Requires
- php: ^8.3
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
README
Routa makes working with Laravel resource routes easy by adding dynamic URL accessors to your Eloquent models. It supports standard resource URLs (index, show, create, update edit, destroy), collection operations, nested route parameters, signed URLs, Blade directives, Artisan generators, and JSON export for frontend consumption.
๐ฆ Installation
Step 1: Install via Composer
composer require dgtlss/routa
Step 2: Publish the Configuration (Optional but Recommended)
php artisan vendor:publish --tag=routa-config
This will create a config/routa.php file where you can customize Routa's behavior.
๐ Quick Start
1. Add the Trait to Your Model
Let's say you have a Product model with resource routes:
// app/Models/Product.php namespace App\Models; use Dgtlss\Routa\Traits\ResourceRoutes; use Illuminate\Database\Eloquent\Model; class Product extends Model { use ResourceRoutes; protected $guarded = []; public static function getResourceRouteBase(): string { return 'products'; // This should match your route name } }
2. Define Your Resource Routes
In your routes/web.php or routes/api.php:
use App\Http\Controllers\ProductController; use Illuminate\Support\Facades\Route; Route::resource('products', ProductController::class);
3. Use the Dynamic URLs
Now you can access all resource URLs directly from your model instances:
$product = Product::find(1); // All available URLs echo $product->index_url; // https://your-app.com/products echo $product->create_url; // https://your-app.com/products/create echo $product->show_url; // https://your-app.com/products/1 echo $product->edit_url; // https://your-app.com/products/1/edit echo $product->update_url; // https://your-app.com/products/1 echo $product->destroy_url; // https://your-app.com/products/1
๐งฐ Features
โ Standard Resource URLs
Routa automatically generates these URL accessors for your models:
| Accessor | Description | Example Usage |
|---|---|---|
show_url |
View a single resource | $product->show_url |
edit_url |
Edit form for a resource | $product->edit_url |
update_url |
Update a resource (PUT/PATCH) | $product->update_url |
destroy_url |
Delete a resource | $product->destroy_url |
index_url |
List all resources | $product->index_url |
create_url |
Create form for new resource | $product->create_url |
โ Collection Operations
Work with multiple models and include route URLs for each item:
| Method | Description | Example Usage |
|---|---|---|
routeMap() |
Get route maps for all models in collection | $products->routeMap() |
withRoutes() |
Append route URLs to each model | $products->withRoutes() |
withRoutes() |
Add routes to individual model | $product->withRoutes() |
toArrayWithRoutes() |
Get model array with routes included | $product->toArrayWithRoutes() |
โ Signed URLs
Generate secure, signed URLs for public access or temporary links:
// Individual signed URLs echo $product->signed_show_url; // Signed version of show_url echo $product->signed_edit_url; // Signed version of edit_url echo $product->signed_update_url; // Signed version of update_url echo $product->signed_destroy_url; // Signed version of destroy_url // Get all signed URLs as an array $signedRoutes = $product->signedRouteMap();
Enable Signed URLs Globally:
Set signed_urls => true in your config/routa.php file, and all URLs will automatically be signed.
Enable Signed URLs per Model:
class Product extends Model { use ResourceRoutes; protected static bool $useSignedUrls = true; // ... rest of the model }
โ Nested Route Parameters
Perfect for nested resources like users/{user}/products/{product}:
// In your routes file Route::resource('users.products', ProductController::class);
// In your Product model class Product extends Model { use ResourceRoutes; protected static array $resourceRouteParams = [ 'user' => 'user_id', // 'user' is the route parameter, 'user_id' is the model attribute ]; public static function getResourceRouteBase(): string { return 'users.products'; } }
Now when you access URLs, Routa automatically includes the user parameter:
$product = Product::find(1); $product->user_id = 5; // The user this product belongs to echo $product->show_url; // https://your-app.com/users/5/products/1 echo $product->edit_url; // https://your-app.com/users/5/products/1/edit
โ Blade Directives
Use routes directly in your Blade templates:
<!-- Basic usage --> <a href="@route($product, 'show')">View Product</a> <a href="@route($product, 'edit')">Edit Product</a> <!-- Default action is 'show' if not specified --> <a href="@route($product)">View Product</a> <!-- Works with any action --> <a href="@route($product, 'destroy')" method="POST">Delete</a>
โ Artisan Commands
Generate a Routa-Enabled Model
php artisan routa:model Product
This creates a new model file with the ResourceRoutes trait already set up:
// app/Models/Product.php namespace App\Models; use Dgtlss\Routa\Traits\ResourceRoutes; use Illuminate\Database\Eloquent\Model; class Product extends Model { use ResourceRoutes; protected $guarded = []; public static function getResourceRouteBase(): string { return 'products'; } }
Export Routes as JSON
Perfect for passing URLs to your frontend JavaScript:
# Basic export php artisan routa:export "App\Models\Product" --id=1 # Pretty-printed JSON php artisan routa:export "App\Models\Product" --id=1 --pretty
Output:
{
"index": "https://your-app.com/products",
"create": "https://your-app.com/products/create",
"show": "https://your-app.com/products/1",
"edit": "https://your-app.com/products/1/edit",
"update": "https://your-app.com/products/1",
"destroy": "https://your-app.com/products/1"
}
โ Route Maps
Get all URLs as an array - perfect for admin panels or bulk operations:
$product = Product::find(1); // Get all standard URLs $routes = $product->routeMap(); /* [ 'index' => 'https://your-app.com/products', 'create' => 'https://your-app.com/products/create', 'show' => 'https://your-app.com/products/1', 'edit' => 'https://your-app.com/products/1/edit', 'update' => 'https://your-app.com/products/1', 'destroy' => 'https://your-app.com/products/1' ] */ // Get all signed URLs $signedRoutes = $product->signedRouteMap(); /* [ 'index' => null, 'create' => null, 'show' => 'https://your-app.com/products/1?signature=abc123...', 'edit' => 'https://your-app.com/products/1/edit?signature=abc123...', 'update' => 'https://your-app.com/products/1?signature=abc123...', 'destroy' => 'https://your-app.com/products/1?signature=abc123...' ] */
โ Collection Support
Work with multiple models and include route URLs for each item - perfect for API responses:
// Get all products with their route URLs $products = Product::all(); // Option 1: Get array of route maps for each product $routeMaps = $products->routeMap(); /* [ ['show' => 'https://your-app.com/products/1', 'edit' => 'https://your-app.com/products/1/edit', ...], ['show' => 'https://your-app.com/products/2', 'edit' => 'https://your-app.com/products/2/edit', ...], ['show' => 'https://your-app.com/products/3', 'edit' => 'https://your-app.com/products/3/edit', ...] ] */ // Option 2: Append routes to each model (Recommended for APIs) $productsWithRoutes = $products->withRoutes(); /* [ { "id": 1, "name": "Product 1", "price": 29.99, "routes": { "index": "https://your-app.com/products", "create": "https://your-app.com/products/create", "show": "https://your-app.com/products/1", "edit": "https://your-app.com/products/1/edit", "update": "https://your-app.com/products/1", "destroy": "https://your-app.com/products/1" } }, { "id": 2, "name": "Product 2", "price": 39.99, "routes": { "index": "https://your-app.com/products", "create": "https://your-app.com/products/create", "show": "https://your-app.com/products/2", "edit": "https://your-app.com/products/2/edit", "update": "https://your-app.com/products/2", "destroy": "https://your-app.com/products/2" } } ] */
Individual Model Methods:
$product = Product::find(1); // Add routes to the model $productWithRoutes = $product->withRoutes(); echo $productWithRoutes->routes['show']; // https://your-app.com/products/1 // Get model as array with routes included $arrayWithRoutes = $product->toArrayWithRoutes(); // Returns: ['id' => 1, 'name' => 'Product', 'routes' => [...], ...]
โ๏ธ Configuration
Publish the config file to customize Routa's behavior:
php artisan vendor:publish --tag=routa-config
config/routa.php
return [ /* |-------------------------------------------------------------------------- | Route Prefix |-------------------------------------------------------------------------- | | This value will be prepended to all route names unless overridden | on the model with `protected static $resourceRoutePrefix`. | Example: 'admin' would make routes like 'admin.products.show' | */ 'route_prefix' => null, /* |-------------------------------------------------------------------------- | Verify Routes Exist |-------------------------------------------------------------------------- | | If enabled, Routa will check if the named route exists using | Route::has(). If it doesn't exist, the URL will return null. | Set to false if you want to generate URLs even when routes don't exist. | */ 'verify_routes_exist' => true, /* |-------------------------------------------------------------------------- | Fallback Prefix for Resource Route Base |-------------------------------------------------------------------------- | | When a model doesn't define `$resourceRouteBase`, this prefix will | be used for auto-generated base route names like 'app.products'. | */ 'base_fallback_prefix' => 'app', /* |-------------------------------------------------------------------------- | Use Signed URLs |-------------------------------------------------------------------------- | | If true, Routa will generate signed URLs where applicable. | This can be overridden per model using $useSignedUrls property. | */ 'signed_urls' => false, ];
Configuration Examples
Admin Panel Routes
// config/routa.php return [ 'route_prefix' => 'admin', ]; // Your model class Product extends Model { use ResourceRoutes; // No need to specify prefix here, it will use the config public static function getResourceRouteBase(): string { return 'products'; } } // Generated route names will be: admin.products.index, admin.products.show, etc.
API Routes
// config/routa.php return [ 'route_prefix' => 'api.v1', ]; // Generated route names will be: api.v1.products.index, api.v1.products.show, etc.
๐งช Testing
Test Helpers
Routa includes a helpful test assertion to verify all routes are working:
// tests/Feature/ProductTest.php public function test_product_routes_are_valid() { $product = Product::factory()->create(); // This will assert that all URLs in routeMap() are valid and non-null $this->assertRoutaRoutesValid($product); }
Running Tests
# Run Routa's test suite composer test # Or using Laravel's test runner php artisan test
๐ง Advanced Usage
API Controllers with Collection Routes
Perfect for building APIs that include route URLs for frontend consumption:
// app/Http/Controllers/ProductController.php class ProductController extends Controller { public function index() { $products = Product::all(); // Return products with route URLs included return response()->json($products->withRoutes()); } public function show(Product $product) { // Return single product with routes return response()->json($product->withRoutes()); } }
API Response Example:
{
"data": [
{
"id": 1,
"name": "Laptop",
"price": 999.99,
"routes": {
"show": "https://api.yourapp.com/products/1",
"edit": "https://api.yourapp.com/products/1/edit",
"update": "https://api.yourapp.com/products/1",
"destroy": "https://api.yourapp.com/products/1"
}
}
]
}
Custom Route Bases
You can customize the route base per model:
class Product extends Model { use ResourceRoutes; public static function getResourceRouteBase(): string { return 'catalog.items'; // Custom route base } } // This will generate routes like: catalog.items.index, catalog.items.show, etc.
Model-Level Configuration
Override global settings per model:
class Product extends Model { use ResourceRoutes; // Override global route prefix protected static string $resourceRoutePrefix = 'admin'; // Override route verification protected static bool $verifyRoutesExist = false; // Enable signed URLs just for this model protected static bool $useSignedUrls = true; public static function getResourceRouteBase(): string { return 'products'; } }
Multiple Nested Parameters
Handle deeply nested routes:
// routes/web.php Route::resource('companies.departments.teams', TeamController::class); // app/Models/Team.php class Team extends Model { use ResourceRoutes; protected static array $resourceRouteParams = [ 'company' => 'company_id', 'department' => 'department_id', ]; public static function getResourceRouteBase(): string { return 'companies.departments.teams'; } } // Usage $team = Team::find(1); $team->company_id = 5; $team->department_id = 10; echo $team->show_url; // /companies/5/departments/10/teams/1
๐ Troubleshooting
URLs Return Null
If your URLs are returning null, check:
- Route Names Match: Ensure your route names match your
getResourceRouteBase()return value - Route Verification: If
verify_routes_existis true, the route must exist - Model Attributes: For nested routes, ensure the required attributes (like
user_id) are set
// Debug route names $routeName = $product->getFullRouteName('show'); dd($routeName, Route::has($routeName));
Route Names Don't Match
Use Laravel's route:list command to see your actual route names:
php artisan route:list --name=product
Nested Routes Not Working
Ensure you've defined the $resourceRouteParams correctly:
// Route: users/{user}/products/{product} // Model needs: protected static array $resourceRouteParams = [ 'user' => 'user_id', // route_parameter => model_attribute ];
๐ฎ Roadmap
-
routa:testgenerator for test scaffolding - Multiple-model export:
php artisan routa:export-all - Optional middleware bindings (e.g. signed + throttled)
- Support for custom route parameters
- Integration with Laravel's route caching
๐ Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development Setup
# Clone the repository git clone https://github.com/dgtlss/routa.git cd routa # Install dependencies composer install # Run tests composer test
๐ License
The MIT License (MIT). Please see License File for more information.
๐ค Support
If you encounter any issues or have questions, please:
- Check the troubleshooting section
- Search existing GitHub Issues
- Create a new issue if needed
Happy coding with Routa! ๐