Giter VIP home page Giter VIP logo

laravel-route-attributes's Introduction

Use PHP 8 attributes to register routes in a Laravel app

Latest Version on Packagist Tests Type Coverage Total Downloads

This package provides annotations to automatically register routes. Here's a quick example:

use Spatie\RouteAttributes\Attributes\Get;

class MyController
{
    #[Get('my-route')]
    public function myMethod()
    {
    }
}

This attribute will automatically register this route:

Route::get('my-route', [MyController::class, 'myMethod']);

Are you a visual learner?

In this video you'll get an introduction to PHP 8 attributes and how this laravel-routes-attributes works under the hood.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Installation

You can install the package via composer:

composer require spatie/laravel-route-attributes

You can publish the config file with:

php artisan vendor:publish --provider="Spatie\RouteAttributes\RouteAttributesServiceProvider" --tag="config"

This is the contents of the published config file:

return [
    /*
     *  Automatic registration of routes will only happen if this setting is `true`
     */
    'enabled' => true,

    /*
     * Controllers in these directories that have routing attributes
     * will automatically be registered.
     *
     * Optionally, you can specify group configuration by using key/values
     */
    'directories' => [
        app_path('Http/Controllers'),

        app_path('Http/Controllers/Web') => [
            'middleware' => ['web']
        ],

        app_path('Http/Controllers/Api') => [
            'prefix' => 'api',
            'middleware' => 'api'
        ],
    ],
];

For controllers outside the applications root namespace directories can also be added using a namespace => path pattern in the directories array. In the following example controllers from Modules\Admin\Http\Controllers will be included.

'directories' => [
    'Modules\Admin\Http\Controllers\\' => base_path('admin-module/Http/Controllers'),
    // Or
    base_path('admin-module/Http/Controllers') => [
        'namespace' => 'Modules\Admin\Http\Controllers\\'
    ],
    app_path('Http/Controllers'),
],

If you are using a directory structure where you co-locate multiple types of files in the same directory and want to be more specific about which files are checked for route attributes, you can use the patterns and not_patterns options. For example, if you are co-locating your tests with your controllers you could use the patterns option to only look in controller files, or you could use not_patterns to configure it to not look in test files for route attributes.

'directories' => [
    base_path('app-modules/Blog') => [
        // only register routes in files that match the patterns
        'patterns' => ['*Controller.php'],
        // do not register routes in files that match the patterns
        'not_patterns => ['*Test.php'],
    ],
],

Usage

The package provides several annotations that should be put on controller classes and methods. These annotations will be used to automatically register routes

Adding a GET route

use Spatie\RouteAttributes\Attributes\Get;

class MyController
{
    #[Get('my-route')]
    public function myMethod()
    {

    }
}

This attribute will automatically register this route:

Route::get('my-route', [MyController::class, 'myMethod']);

Using other HTTP verbs

We have left no HTTP verb behind. You can use these attributes on controller methods.

#[Spatie\RouteAttributes\Attributes\Post('my-uri')]
#[Spatie\RouteAttributes\Attributes\Put('my-uri')]
#[Spatie\RouteAttributes\Attributes\Patch('my-uri')]
#[Spatie\RouteAttributes\Attributes\Delete('my-uri')]
#[Spatie\RouteAttributes\Attributes\Options('my-uri')]

Resource controllers

To register a resource controller, use the Resource attribute as shown in the example below.

You can use only or except parameters to manage your resource routes availability.

You can use parameters parameter to modify the default parameters set by the resource attribute.

You can use the names parameter to set the route names for the resource controller actions. Pass a string value to set a base route name for each controller action or pass an array value to define the route name for each controller action.

You can use shallow parameter to make a nested resource to apply nesting only to routes without a unique child identifier (index, create, store).

You can use apiResource boolean parameter to only include actions used in APIs. Alternatively, you can use the ApiResource attribute, which extends the Resource attribute class, but the parameter apiResource is already set to true.

Using Resource attribute with Domain, Prefix and Middleware attributes works as well.

use Spatie\RouteAttributes\Attributes\Resource;

#[Prefix('api/v1')]
#[Resource(
    resource: 'photos.comments',
    apiResource: true,
    shallow: true,
    parameters: ['comments' => 'comment:uuid'],
    names: 'api.v1.photoComments',
    except: ['destroy'],
)]
// OR #[ApiResource(resource: 'photos.comments', shallow: true, ...)]
class PhotoCommentController
{
    public function index(Photo $photo)
    {
    }

    public function store(Request $request, Photo $photo)
    {
    }

    public function show(Comment $comment)
    {
    }

    public function update(Request $request, Comment $comment)
    {
    }
}

The attribute in the example above will automatically register following routes:

Route::get('api/v1/photos/{photo}/comments', [PhotoCommentController::class, 'index'])->name('api.v1.photoComments.index');
Route::post('api/v1/photos/{photo}/comments', [PhotoCommentController::class, 'store'])->name('api.v1.photoComments.store');
Route::get('api/v1/comments/{comment}', [PhotoCommentController::class, 'show'])->name('api.v1.photoComments.show');
Route::match(['put', 'patch'], 'api/v1/comments/{comment}', [PhotoCommentController::class, 'update'])->name('api.v1.photoComments.update');

Using multiple verbs

To register a route for all verbs, you can use the Any attribute:

#[Spatie\RouteAttributes\Attributes\Any('my-uri')]

To register a route for a few verbs at once, you can use the Route attribute directly:

#[Spatie\RouteAttributes\Attributes\Route(['put', 'patch'], 'my-uri')]

Specify a route name

All HTTP verb attributes accept a parameter named name that accepts a route name.

use Spatie\RouteAttributes\Attributes\Get;

class MyController
{
    #[Get('my-route', name: "my-route-name")]
    public function myMethod()
    {
    }
}

This attribute will automatically register this route:

Route::get('my-route', [MyController::class, 'myMethod'])->name('my-route-name');

Adding middleware

All HTTP verb attributes accept a parameter named middleware that accepts a middleware class or an array of middleware classes.

use Spatie\RouteAttributes\Attributes\Get;

class MyController
{
    #[Get('my-route', middleware: MyMiddleware::class)]
    public function myMethod()
    {

    }
}

This annotation will automatically register this route:

Route::get('my-route', [MyController::class, 'myMethod'])->middleware(MyMiddleware::class);

To apply middleware on all methods of a class you can use the Middleware attribute. You can mix this with applying attribute on a method.

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;

#[Middleware(MyMiddleware::class)]
class MyController
{
    #[Get('my-route')]
    public function firstMethod()
    {
    }

    #[Get('my-other-route', middleware: MyOtherMiddleware::class)]
    public function secondMethod()
    {
    }
}

These annotations will automatically register these routes:

Route::get('my-route', [MyController::class, 'firstMethod'])->middleware(MyMiddleware::class);
Route::get('my-other-route', [MyController::class, 'secondMethod'])->middleware([MyMiddleware::class, MyOtherMiddleware::class]);

Specifying a prefix

You can use the Prefix annotation on a class to prefix the routes of all methods of that class.

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;

#[Prefix('my-prefix')]
class MyController
{
    #[Get('my-get-route')]
    public function myGetMethod()
    {
    }

    #[Post('my-post-route')]
    public function myPostMethod()
    {
    }
}

These annotations will automatically register these routes:

Route::get('my-prefix/my-get-route', [MyController::class, 'myGetMethod']);
Route::post('my-prefix/my-post-route', [MyController::class, 'myPostMethod']);

Specifying a domain

You can use the Domain annotation on a class to prefix the routes of all methods of that class.

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Domain;

#[Domain('my-subdomain.localhost')]
class MyController
{
    #[Get('my-get-route')]
    public function myGetMethod()
    {
    }

    #[Post('my-post-route')]
    public function myPostMethod()
    {
    }
}

These annotations will automatically register these routes:

Route::get('my-get-route', [MyController::class, 'myGetMethod'])->domain('my-subdomain.localhost');
Route::post('my-post-route', [MyController::class, 'myPostMethod'])->domain('my-subdomain.localhost');

Specifying a domain from a config key

There maybe a need to define a domain from a configuration file, for example where your subdomain will be different on your development environment to your production environment.

// config/domains.php
return [
    'main' => env('SITE_URL', 'example.com'),
    'subdomain' => env('SUBDOMAIN_URL', 'subdomain.example.com')
];
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\DomainFromConfig;

#[DomainFromConfig('domains.main')]
class MyController
{
    #[Get('my-get-route')]
    public function myGetMethod()
    {
    }
}

When this is parsed, it will get the value of domains.main from the config file and register the route as follows;

Route::get('my-get-route', [MyController::class, 'myGetMethod'])->domain('example.com');

Scoping bindings

When implicitly binding multiple Eloquent models in a single route definition, you may wish to scope the second Eloquent model such that it must be a child of the previous Eloquent model.

By adding the ScopeBindings annotation, you can enable this behaviour:

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\ScopeBindings;

class MyController
{
    #[Get('users/{user}/posts/{post}')]
    #[ScopeBindings]
    public function getUserPost(User $user, Post $post)
    {
        return $post;
    }
}

This is akin to using the ->scopeBindings() method on the route registrar manually:

Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    return $post;
})->scopeBindings();

By default, Laravel will enabled scoped bindings on a route when using a custom keyed implicit binding as a nested route parameter, such as /users/{user}/posts/{post:slug}.

To disable this behaviour, you can pass false to the attribute:

#[ScopeBindings(false)]

This is the equivalent of calling ->withoutScopedBindings() on the route registrar manually.

You can also use the annotation on controllers to enable implicitly scoped bindings for all its methods. For any methods where you want to override this, you can pass false to the attribute on those methods, just like you would normally.

Specifying where

You can use the Where annotation on a class or method to constrain the format of your route parameters.

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Where;
use Spatie\RouteAttributes\Attributes\WhereAlphaNumeric;

#[Where('my-where', '[0-9]+')]
class MyController
{
    #[Get('my-get-route/{my-where}')]
    public function myGetMethod()
    {
    }

    #[Post('my-post-route/{my-where}/{my-alpha-numeric}')]
    #[WhereAlphaNumeric('my-alpha-numeric')]
    public function myPostMethod()
    {
    }
}

These annotations will automatically register these routes:

Route::get('my-get-route/{my-where}', [MyController::class, 'myGetMethod'])->where(['my-where' => '[0-9]+']);
Route::post('my-post-route/{my-where}/{my-alpha-numeric}', [MyController::class, 'myPostMethod'])->where(['my-where' => '[0-9]+', 'my-alpha-numeric' => '[a-zA-Z0-9]+']);

For convenience, some commonly used regular expression patterns have helper attributes that allow you to quickly add pattern constraints to your routes.

#[WhereAlpha('alpha')]
#[WhereAlphaNumeric('alpha-numeric')]
#[WhereIn('in', ['value1', 'value2'])]
#[WhereNumber('number')]
#[WhereUlid('ulid')]
#[WhereUuid('uuid')]

Specifying a group

You can use the Group annotation on a class to create multiple groups with different domains and prefixes for the routes of all methods of that class.

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Domain;

#[Group(domain: 'my-subdomain.localhost', prefix: 'my-prefix')]
#[Group(domain: 'my-second-subdomain.localhost', prefix: 'my-second-prefix')]
class MyController
{
    #[Get('my-get-route')]
    public function myGetMethod()
    {
    }

    #[Post('my-post-route')]
    public function myPostMethod()
    {
    }
}

These annotations will automatically register these routes:

Route::get('my-get-route', [MyController::class, 'myGetMethod'])->prefix('my-prefix')->domain('my-subdomain.localhost');
Route::post('my-post-route', [MyController::class, 'myPostMethod'])->prefix('my-prefix')->domain('my-subdomain.localhost');
Route::get('my-get-route', [MyController::class, 'myGetMethod'])->prefix('my-second-prefix')->domain('my-second-subdomain.localhost');
Route::post('my-post-route', [MyController::class, 'myPostMethod'])->prefix('my-second-prefix')->domain('my-second-subdomain.localhost');

Specifying defaults

You can use the Defaults annotation on a class or method to define the default values of your optional route parameters.

use Spatie\RouteAttributes\Attributes\Defaults;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;

#[Defaults('param', 'controller-default')]
class MyController extends Controller
{
    #[Get('my-get-route/{param?}')]
    public function myGetMethod($param)
    {
    }

    #[Post('my-post-route/{param?}/{param2?}')]
    #[Defaults('param2', 'method-default')]
    public function myPostMethod($param, $param2)
    {
    }

    #[Get('my-default-route/{param?}/{param2?}/{param3?}')]
    #[Defaults('param2', 'method-default-first')]
    #[Defaults('param3', 'method-default-second')]
    public function myDefaultMethod($param, $param2, $param3)
    {
    }

    #[Get('my-override-route/{param?}')]
    #[Defaults('param', 'method-default')]
    public function myOverrideMethod($param)
    {
    }
}

These annotations will automatically register these routes:

Route::get('my-get-route/{param?}', [MyController::class, 'myGetMethod'])->setDefaults(['param', 'controller-default']);
Route::post('my-post-route/{param?}/{param2?}', [MyController::class, 'myPostMethod'])->setDefaults(['param', 'controller-default', 'param2' => 'method-default']);
Route::get('my-default-route/{param?}/{param2?}/{param3?}', [MyController::class, 'myDefaultMethod'])->setDefaults(['param', 'controller-default', 'param2' => 'method-default-first', 'param3' => 'method-default-second']);
Route::get('my-override-route/{param?}', [MyController::class, 'myOverrideMethod'])->setDefaults(['param', 'method-default']);

With Trashed

You can use the WithTrashed annotation on a class or method to enable WithTrashed bindings to the model. You can explicitly override the behaviour using WithTrashed(false) if it is applied at the class level

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\WithTrashed;

#[WithTrashed]
class MyController extends Controller
{
    #[Get('my-get-route/{param}')]
    #[WithTrashed]
    public function myGetMethod($param)
    {
    }

    #[Post('my-post-route/{param}')]
    #[WithTrashed(false)]    
    public function myPostMethod($param)
    {
    }

    #[Get('my-default-route/{param}')]
    public function myDefaultMethod($param)
    {
    }    
}

These annotations will automatically register these routes:

Route::get('my-get-route/{param}', [MyController::class, 'myGetMethod'])->WithTrashed();
Route::post('my-post-route/{param}', [MyController::class, 'myPostMethod'])->withTrashed(false);
Route::get('my-default-route/{param}', [MyController::class, 'myDefaultMethod'])->withTrashed();

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

laravel-route-attributes's People

Contributors

adrianmrn avatar asharifineyestani avatar brendt avatar christian98 avatar danielsetreus avatar dependabot[bot] avatar ejunker avatar freekmurze avatar github-actions[bot] avatar hepplerdotnet avatar idanieldrew avatar jaulz avatar laravel-shift avatar leonardocaldas avatar midnite81 avatar mikerockett avatar nielsvanpach avatar obuchmann avatar osbre avatar patinthehat avatar radiergummi avatar rico avatar robertdrakedennis avatar rodrigopedra avatar samueljtaylor avatar serkanerip avatar shuvroroy avatar sirmathays avatar themrcatsu avatar tofandel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-route-attributes's Issues

Routes are registered multiple

My config file:

<?php

return [
    /*
     *  Automatic registration of routes will only happen if this setting is `true`
     */
    'enabled' => true,

    /*
     * Controllers in these directories that have routing attributes
     * will automatically be registered.
     *
     * Optionally, you can specify group configuration by using key/values
     */
    'directories' => [
        app_path('Http/Controllers'),

        app_path('Http/Controllers/Api') => [
           'prefix' => 'api',
           'middleware' => 'api',
        ],

    ],

    /**
     * This middleware will be applied to all routes.
     */
    'middleware' => [
        \Illuminate\Routing\Middleware\SubstituteBindings::class
    ]
];

The controllers under the "Api" folder are registered multiple. I think this is because of depth.

https://symfony.com/doc/current/components/finder.html#directory-depth

Symfony\Component\Finder\Exception\DirectoryNotFoundException The "src/App/Http/Controllers" directory does not exist.

Does this rely on the presence of App\Http\Controllers namespace?

I am trying to use this on a Distributed project and I have no App\Http namespace. I moved the kernel into the App namespace (similar to your beyond crud demo) and I put all the controllers each into their respective domains, and none of them extend the default App\Http\Controllers\Controller so I eliminated it simply because I don't have a use for it.

Is whatever is causing this exception on post-autoload-dump after installing this package crucial to its functionality?

use form validation with Route Attributes

Hey,
i want to use this excellent package for my current project, where i am currently testing out some ideas.
What i encountered is, that this package currently does not work when using it with FormRequest Validators.

Steps to reproduce.

  1. Fresh Laravel Installation

  2. create a model

  3. create a controller

  4. create a request object

  5. install spatie/laravel-route-attributes package

  6. create a get and a post route in the controller

  7. validate data in post action request

  8. output form errors

     class RegistrationController extends Controller
     {
            #[Get('register', name: "register.view")]
            public function view()
            {
                return response()->view('register.view');
            }
    
           #[Post('register', name: "register.store")]
           public function store(PublicRegisterRequest $request)
           {
               exit("called");
           }
     }
    

the view looks like this:

   @extends('frontend')
  
   @section('content')
      <form action="{{ route('register.store') }}" method="post">
          @csrf
  
          @include('partials.form.elements.text', [
              'name' => 'name',
              'label' => __('public/register.name')
          ])
  
          @include('partials.form.elements.email', [
             'name' => 'email',
             'label' => __('public/register.email')
          ])
  
          @include('partials.form.elements.confirmed', [
             'name' => 'password',
             'label' => __('public/register.password')
          ])
  
          @include('partials.form.elements.checkbox', [
            'name' => 'agb',
            'label' => 'AGB'
          ])
  
          @include('partials.form.elements.submit', [
            'label' => __('public/register.submit'),
          ])
  
      </form>
  @endsection

and for example the text helper looks like this

  <div class="form-group">
      <label for="{{ $name }}">
          {{ $label }}
      </label>
  
      <input type="text" value="{{ old($name) }}" name="{{ $name }}" class="form-control" id="{{ $name }}" />
      @if (isset($errors) && $errors->has($name))
          {{ $errors->first($name) }}
      @endif
  </div>

The problem here is, that $errors are always not present.
As soon as i remove the package, they will be outputted again

Laravel Version: 8.46.0
PHP Version: 8.0.7

Any hints how to get it to work? am i missing something?

EDIT:

When setting enabled to false and registrating the routes by myself in the web.php file, the validation works as expected.

EDIT 2:

Also using $this->validate inside the controller action does not work.
So currently with your package outputting validation errors is not possible.

   #[Post('register', name: "register.store")]
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|unique:users,name',
            'email' => 'required|unique:users,email|email',
            'password' => 'required|min:8|confirmed',
            'agb' => 'required'
        ]);


        exit("called");
    }

Can't get it to work

I just setup a new Laravel project to play with this package.

My dev-box:
Centos 8.3.2011
PHP 8.0.0
Laravel: 8.5.5 w/ Framework 8.19.0

Steps I took:
composer create-project laravel/laravel test
cd test
composer require spatie/laravel-route-attributes
composer update
php artisan vendor:publish --provider="Spatie\RouteAttributes\RouteAttributesServiceProvider" --tag="config"
php artisan make:controller HomeController

config/route-attributes.php

<?php

return [
    /*
     *  Automatic registration of routes will only happen if this setting is `true`
     */
    'enabled' => true,

    /*
     * Controllers in these directories that have routing attributes
     * will automatically be registered.
     */
    'directories' => [
        app_path('Http/Controllers'),
    ],
];

app/Http/Controllers/HomeController.php

<?php

namespace App\Http\Controllers;

use Spatie\RouteAttributes\Attributes\Get;
use Illuminate\Http\Request;

class HomeController extends Controller
{
    #[Get('foo')]
    public function index()
    {
        return 'Foo';
    }
}

php artisan serve

Visiting http://127.0.0.1:8000/foo returns a 404
Any idea what's missing?

How to use nested resources?

Hello,

I'm wondering how I can use nested resources?

The normal way is to define the deeper resources first, then define the higher resource after.
But this can't be done using attributes because there is no order/priority.

Route::group(['prefix' => 'foo'], function() {
        Route::resource('/bar', Foo\BarController::class);
        Route::resource('/', FooController::class);
});

So:
A request to /foo/bar must land in Foo\BarController.
A request to /foo or /foo/(anything else than bar) must land in FooController.

Is there a way to do this?

Middleware order

Hey all and thanks a lot for this great package.

One thing surprised me: when using the Middleware attribute with several middlewares, it seems that the middlewares are applied in reverse order.

#[Middleware([MiddlewareA::class, MiddlewareB::class, MiddlewareC::class ])]

In this case, the first middleware Laravel will hit is MiddlewareC, then MiddlewareB and finally MiddlewareA.

Is this a normal behavior that might be worth documenting or some kind of bug?

Tested on v1.20.1.

localized url

I use localized url in my project, like that:
example.com (redirect to example.com/en )
example.com/en
example.com/en/about
example.com/de
example.com/de/about

I use https://github.com/mcamara/laravel-localization package for this, but i cannot configure it for using with laravel-route-attributes.
It use dynamicaly 'prefix' => LaravelLocalization::setLocale() in route, but laravel-route-attributes not support it.
Maybe there is a way to make friends these two packages?

Use Route::?()->where with attributes

Hello,
With the normal way to declare a route with laravel, I would do this :

Route::get('/member/{id}', function (int $id) {
    return $id;
})->where(['id' => '[0-9]+']);

I wanted to know if I can use this with the attributes.

Maybe we can use a method like this :

#[Get('/member/{id}', params: [
    'id' => '[0-9]+'
])]
public function memberId(int $id) {
...

Controllers from namespace outside of app

Our application setup is a bit... odd, for legacy reasons. In this example I'd like to load annotated controllers from a namespace outside of the application. In essense trying to define directories like

'directories' => [
        app_path('Http/Controllers/Test'),
        '/home/parent/Http/Controllers/Test/',
]

where app_path gives a sub directory like /home/yada/yada/wtf/.

Problem is the namespace, as the package seems to try and determine the namespace based on its location, with the applications root namespace. So it would try and find controllers under /home/parent using namespace App\Home\parent\Http\Controllers\Test\PhpTestController.

Would it be possible to, optionally, define directories with a pre-defined root namespace? Perhaps like

'directories' => [
        app_path('Http/Controllers/Test'),
        [
            'dir' => '/home/parent/Http/Controllers/Test/',
            'rootNs' => 'ParentApp\Http\Controllers\Test',
        ],
]

`Any` method

Hi,
It would be great if we could use Any method too which supports all of methods...
like:

use Spatie\RouteAttributes\Attributes\Any;

class MyController
{
    #[Any('my-route')]
    public function myMethod()
    {

    }
}

Middleware usage, with Laravel 8

Is the middleware section in config is full replacement of Kernel.php array?

I tried to move middlewares from Kernel.php to config file, and found a bug, for me it was a cors issue - i mean this middleware was not worked at all.
So in situation when i moved Cors middleware to config - its not working
Leave it in Kernel - working

For my own middleware, it works well in both places but also if i leave it in Kernel & config, it will be called twice

// UDPATE
Also SubstituteBindings, is not working if i put it in Kernel, and start work if i put it to config, can anyone explain why?

If its not a full replacement, then why we need it in config section?

my versions:
"laravel/framework": "^8.12"
"spatie/laravel-route-attributes": "^1.9"

Attributes not working.

Hey Spatie,

Currently im trying this excellent package. On a fresh application. But i keep on stumbling on that the attribute is not working and always returning a 404 response.

My code:

<?php

namespace App\Http\Controllers;

use Spatie\RouteAttributes\Attributes\Get;

class HomeController extends Controller
{
    #[Get('home' name: "home")]
    public function index()
    {
        return view('home');
    }
}

Is this possible a bug?

Policy attributes

When we use non-standard controller method, we override resourceAbilityMap and resourceMethodsWithoutModels so controller auto run policy for custom these methods.

I think we can use attribute to simulate this behavior?

How to pass parameters to middleware

Hi. How can I use middleware with parameters? For example, I have middleware handle(Request $request, Closure $next, string $role) and I cannot find how to pass role parameter value.

Feature: Domain attribute

Hi there, I was wondering what your thoughts on adding domain as one of the argument / features for routing? I'm currently looking into using this for a tenancy based project and was wondering on what your thoughts would be for it. Thanks!

Extra method for resource

How I can add extra method for resource route ?
Without attribute route we can define route before resource, but in here how we can do that ?

Route::get('foo/bar', 'FooController@bar');
Route::resource('foo', 'FooController');

Is there any priority for routes ?

[Discussion] Code Restructure

Hey,
first, thanks for creating this package! I think this could be a really handy package for developing with PHP 8 and Laravel!

But I have a few concerns about the Code structure. I would like to hear your thoughts about that.
Mainly, what was the intention to put all logic inside the RouteRegistrar.php and not inside the Attributes itself?
In my opinion, seperating the code, would give the package the ability to receive and maintain new features in an easier way.

My preference would be, that there are Interfaces for Class and Method Attributes, that force you to implement a given method like this:

interface RouteMethod {
    public function executeMethodAttribute(ReflectionClass $class, ReflectionMethod $method, ?Illuminate\Routing\Route $route);
}

interface RouteClass {
    public function executeClassAttribute(ReflectionClass $class, ?Illuminate\Routing\Route $route);
}

All Attribute related (and most likely very specific) Code would go inside these Methods inside their Attribute-Class.
An Sample Attribute could look something like this:

#[Attribute(Attribute::TARGET_CLASS)]
class Middleware implements RouteClass
{
    public array $middleware = [];

    public function __construct(string|array $middleware = [])
    {
        $this->middleware = Arr::wrap($middleware);
    }

    public function executeClassAttribute(ReflectionClass $class, ?Illuminate\Routing\Route $route) {
        // do some middleware stuff here
    }
}

This way the RouteRegistrar.php would not get cluttered and hard to maintain. A problem with this is the start, the way a new Route will be instantiated. (I do not know if the Attributes are always in the same order, as they appear in the Controller-Classes...)

I know this is not a finished concept and there has to be some more thinking to be done.
Now I want to know your opinions ;)

Routes with domains are serving the wrong controller

Issue:

Because Laravel processes routes on a first-come-first-serve basis, if you have routes with the same URI on different domains, it doesn't matter which domain you are trying to request, Laravel with serve you the first route that matches that URI.

Demo:

With this route definition if you're trying to request admin.localhost/test Laravel will serve you localhost/test.

Route::get('/test', function () {
    //
});

Route::group(['domain' => 'admin.localhost'], function () {
    Route::get('/test', function () {
        //
    });
});

That's why we will have to define the subdomain group first.

With this route definition, Laravel will serve correct routes.

Route::group(['domain' => 'admin.localhost'], function () {
    Route::get('/test', function () {
        //
    });
});

Route::get('/test', function () {
    //
});

Conclusion:

Back to why I'm posting this issue... I know this is not an issue with this package per se, but using it, we are losing the ability to control the order of defined routes.

We could solve this by sorting the collected routes by ones that have a domain defined first, and then registering them.

Currently, I'm unable to fully utilize this great tool, because I have subdomains in my project, but if I find some spare time I'll try to PR a "fix".

Regards.

specifying a controller

how we can specify Controller resource in controller class?

for example you suppose we want to make this route:

Route::resource('/post',Post::class)->names('posts);

Prefix parameter type specification

Hi there,

I was going through the documentation and I couldn't find any info about prefix type specifications. Here's an example of what I'm trying to achieve:

#[OpenApi\PathItem]
#[Routes\Prefix('api/v1/{category}/companies')]
class CompanyController extends Controller
{
    ...
}

So when the OpenAPI documentation is generated it, I assume, defaults to string type for this parameter {category} inside the prefix. Can I customize it to say int or integer?

Any help is much appreciated in advance.

Middleware inheritance

Now that I've converted our entire API to use route attributes, I've noticed some patterns that I would like to improve. In particular, it's a little cumbersome that overriding middleware set on the controller level is impossible:

use Spatie\RouteAttributes\Attributes as Routing;

#[Routing\Middleware('throttle:200')] 
class FooController
{
    #[Routing\Middleware('throttle:2')]      <-- will add another throttle middleware
    public function expensiveCall(): Response;
}

While this makes perfect sense, it requires me to define the middleware on every method explicitly, only because I have single culprit with different requirements. I've been thinking about how to solve this, and would like to suggest adding a WithoutMiddleware attribute:

use Spatie\RouteAttributes\Attributes as Routing;

#[Routing\Middleware('throttle:200')] 
class FooController
{
    #[Routing\WithoutMiddleware('throttle:200')]  <-- removes the middleware inherited from the controller
    #[Routing\Middleware('throttle:2')]
    public function expensiveCall(): Response;
}

This isn't really elegant, though. Would anyone have a better idea?

Routes are stil registered even after caching them

Hello,

I'm not sure it's an "issue" strictly saying, but it would be nice to avoid re-parsing controller files when routes are cached. I don't know how fast registering routes is, but I think it's always a gain to avoid re-parsing.

In service provider class, this method is defined to register routes:

protected function registerRoutes(): void
{
    if (! config('route-attributes.enabled')) {
        return;
    }

    $routeRegistrar = (new RouteRegistrar(app()->router))
        ->useRootNamespace(app()->getNamespace())
        ->useMiddleware(config('route-attributes.middleware') ?? []);

    collect($this->getRouteDirectories())->each(fn (string $directory) => $routeRegistrar->registerDirectory($directory));
}

Laravel Application class offers public method routesAreCached() to find out if route registration is required, so I think it's not difficult to add this check. I'd like to do it and make a pull request, but I don't know what kind of test can ensure this behavior.

So I prefer let you have a look, to avoid an useless pull request without any test.

Regards.
Raphael Jorel

Route model binding doesn't work with Resource attribute

When using the Resource attribute, implicit route model binding does not work.

For example:

namespace App\Http\Controllers;

use App\Models\User;
use Spatie\RouteAttributes\Attributes\Resource;

#[Resource('user')]
class UserController extends Controller
{
    public function show(User $user)
    {
        dd($user);
    }
}

When visiting /user/1 the expected result would be an instance of the User model with id of 1.

The actual result is an empty instance of the User model. (Same as returning new User()


If the model's type hint is removed we just get the passed id

public function show($user)
{
    dd($user);
}

Visiting /user/1 simply returns "1"


The desired result can be achieved using the Get attribute:

#[Get('/user/{user}')]
public function show(User $user)
{
    dd($user);
}

Visiting /user/1 correctly returns an instance of the User model with id of 1

This works both with and without including the Resource attribute in the controller.

Error installing in Laravel 10

Goodnight.
I'm having a problem installing the library in Laravel 10, after the installation is complete I'm getting the error "Unable to detect application namespace.".
The same stops happening after removing the library.

[Feature request] Group by directory

A common project structure is to have 3 different types of routes

Eg:

Controllers/Admin
Controllers/Front
Controllers/Api

They would normally be configured using the RouteServiceProvider

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->namespace($this->namespace)
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->namespace($this->namespace)
                ->group(base_path('routes/web.php'));

            Route::middleware(['web', 'backend'])
                ->namespace($this->namespace)
                ->prefix('admin')
                ->group(base_path('routes/admin.php'));
        });

There doesn't seem to be a way to configure the package to have this kind of behavior except than to specify the prefix and middlewares on top of each controller in those directories

It would be much nicer if there was a way to configure this in the config file

Cannot declare class because the name is already in use

I have multiple directories with the Controller base class like that:

  • App\Modules\Admin
    • Controller
    • DashboardController
  • App\Modules\Api
    • Controller
    • UserController

When I specified all these directories in the 'directories' parameter (route-attributes.php file) I got the following error:

Cannot declare class App\Modules\Api\Controllers\Controller, because the name is already in use

So, it is impossible to use this package when an application has controller classes with the same name.

PHP 8.0.11
Laravel 8.64

Route parameter model binding not working

Hi!
I'm trying to use route with parameter in it. But I always get an empty model injected in controller method.

app/Http/Controllers/UserController.php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\JsonResponse;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Prefix;

#[Prefix('api')]
class UserController extends Controller
{
    #[Get('user/{user}')]
    public function show(User $user): JsonResponse
    {
        return response()->json($user);
    }
}

Request to /api/user/1 returns empty result.
User with id 1 exists in database.

Support for Single Action Controllers or Similar

Hi everyone

Single Action Controllers are already supported by adding the Attribute to the "__invoke" function

https://laravel.com/docs/10.x/controllers#single-action-controllers

What we're trying to do is:

  • Describing a Controller or Similar class just by the classname
  • pass the name of the class to the attribute
  • register the Controller/Class - based on the class_basename

based on the example from the docs something like:

<?php
 
namespace App\Http\Controllers;
 
use App\Models\User;
use Illuminate\Http\Response;
 
#[Route]
class ProvisionServer extends Controller
{
    /**
     * Provision a new web server.
     */
    public function __invoke()
    {
        // ...
    }
}

For this to work - the registerResource function could be extended or the registerRoutes adapted a bit:

  protected function registerRoutes(ReflectionClass $class, ClassRouteAttributes $classRouteAttributes): void
    {
        $this->registerAttribute($class, $classRouteAttributes, $class);

        foreach ($class->getMethods() as $method) {
            $this->registerAttribute($class, $classRouteAttributes, $method);
        }
    }

I'm happy to also open a PR for this :) -> working branch here -> main...FabianHippmann:laravel-route-attributes:main

Feature Request: Allow routes to be bound from namespaces other than `App\`.

It appears as though it's not currently bind routes inside a custom Laravel package. This would be a great feature.

Perhaps the config file fomat could be updated to include namespaces along with the directories?

<?php

return [
    /*
     *  Automatic registration of routes will only happen if this setting is `true`
     */
    'enabled' => true,

    /*
     * Controllers in these directories that have routing attributes
     * will automatically be registered.
     */
    'directories' => [
        'App\Http\Controllers' => app_path('Http/Controllers'),
        'User\Package\Http\Controllers => 'base_path('vendor/user/package/src/Http/Controllers'),
    ],

    /**
     * This middleware will be applied to all routes.
     */
    'middleware' => [
        \Illuminate\Routing\Middleware\SubstituteBindings::class
    ]
];

Thanks, and keep up the awesome work!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.