phplaravellaravel-8laravel-routinglaravel-service-container

Laravel route resolve custom data type


I have the following routes in routes/api.php:

Route::get('items/{item}', function(Guid $item) {...});
Route::get('users/{user}', function(Guid $user) {...});

Since Guid is a custom type, how can I resolve that via dependency injection? As shown, the route parameter {item} differs from the callback parameter type-hint:Guid so it can not be automatically resolved.


That's what I've tried in app/Providers/AppServiceProvider.php:

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(Guid::class, function(Application $app, array $params) {
            return Guid::fromString($params[0]);
        });
    }
}

I'd expect $params to be something like this: [ 'item' => 'guid' ] -- but it is: [].


Solution

  • You can make use of explicit binding Laravel Routing:

    in RouteServiceProvider::boot():

    public function boot()
    {
        Route::model('item', Guid $item);
        Route::model('user', Guid $user);
    }
    

    If Guid is not a model use a Closure to map onto the string:

    Route::bind('user', function ($value) {
       return Guid::fromString($value);
    });
    

    UPDATED

    And I found another way, much better - implement UrlRoutable contract Lavaravel API:

    <?php
    
    namespace App\Models;
    
    use Illuminate\Contracts\Routing\UrlRoutable;
    
    class Guid implements UrlRoutable
    {
        private string $guid;
    
        public function setGuid(string $guid)
        {
            $this->guid = $guid;
            return $this;
        }
    
        public function getGuid(): string
        {
            return $this->guid;
        }
    
        public static function fromString(string $guid): self
        {
            //you cannot set props from constructor in this case
            //because binder make new object of this class
            //or you can resolve constructor depts with "give" construction in ServiceProvider
            return (new self)->setGuid($guid);
        }
        public function getRouteKey()
        {
            return $this->guid;
        }
    
        public function getRouteKeyName()
        {
            return 'guid';
        }
    
        public function resolveRouteBinding($value, $field = null)
        {
            //for using another "fields" check documentation
            //and maybe another resolving logic
            return self::fromString($value);
        }
    
        public function resolveChildRouteBinding($childType, $value, $field)
        {
            //or maybe you have relations
            return null;
        }
    }
    

    And, with this, you can use routes like you want as Guid now implements UrlRoutable and can turn {item} (or whatever) URL-path sub-string markers into Guids per dependency injection (by the type-hint as you asked for it):

    Route::get('items/{item}', function(Guid $item) {
       return $item->getGuid();
    });
    
    

    BTW: NEVER EVER use closures in routes as you cannot cache closure routes - and routes are good to be optimized, and caching helps with that in Laravel routing.