dgtlss/routa

Automatically generate resource route URLs on Laravel models using a trait.

Maintainers

Details

github.com/dgtlss/routa

Source

Issues

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/dgtlss/routa

1.0.0 2025-10-01 21:59 UTC

This package is auto-updated.

Last update: 2025-10-01 22:02:52 UTC


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.

Latest Version on Packagist Total Downloads GitHub Tests License Laravel

๐Ÿ“ฆ 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:

  1. Route Names Match: Ensure your route names match your getResourceRouteBase() return value
  2. Route Verification: If verify_routes_exist is true, the route must exist
  3. 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:

  1. Check the troubleshooting section
  2. Search existing GitHub Issues
  3. Create a new issue if needed

Happy coding with Routa! ๐Ÿš€