dgtlss / routa
Automatically generate resource route URLs on Laravel models using a trait.
Installs: 0
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_exist
is 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:test
generator 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! ๐