├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Attribute │ ├── ClassMiddleware.php │ └── Middleware.php ├── AttributeMiddleware.php └── BaseController.php └── tests └── Middleware ├── Fixture └── FooController.php └── MiddlewareTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | .phpunit.cache 4 | composer.lock 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Saeed-Pooyanfar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel #[Annotation] 2 | 3 | ### Introduction 4 | 5 | PHP 8.0 release was a revolution for the language. 6 | It brings cool features like [`Named arguments`](https://www.php.net/releases/8.0/en.php#named-arguments), [`Attributes`](https://www.php.net/releases/8.0/en.php#attributes), [`Constructor property`](https://www.php.net/releases/8.0/en.php#constructor-property-promotion) and ... 7 | PHP 8.1 brings even more exciting features like [`Enumerations`](https://www.php.net/releases/8.1/en.php#enumerations), [`New in initializers`](https://www.php.net/releases/8.1/en.php#new_in_initializers), [`Array unpacking`](https://www.php.net/releases/8.1/en.php#array_unpacking_support_for_string_keyed_arrays) and ... 8 | **The idea of this package is using PHP [`Attribute`](https://www.php.net/manual/en/language.attributes.overview.php) in a laravel project.** 9 | 10 | ### Installing 11 | 12 | ```bash 13 | composer require saeedpooyanfar/laravel-annotation 14 | ``` 15 | 16 | ### Setup 17 | 18 | In `App\Http\Controllers\Controller::class` : 19 | Replace `use Illuminate\Routing\Controller as BaseController;` 20 | With `use LaravelAnnotation\BaseController;` 21 | 22 | Or if you don't want to change your BaseController you can: 23 | 24 | Use `LaravelAnnotation\AttributeMiddleware` trait 25 | In `App\Http\Controllers\Controller::class` 26 | 27 | ### Middleware attribute 28 | 29 | Here is an example that how you can use `Middleware` attribute in a laravel controller: 30 | 31 | ```php 32 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Attribute/ClassMiddleware.php: -------------------------------------------------------------------------------- 1 | getAttributes("middleware"); 22 | return array_merge($middlewares, $this->getMiddlewaresByAttributes()); 23 | } 24 | 25 | /** 26 | * Get the controller middlewares by attributes 27 | * 28 | * @see Middleware 29 | * 30 | * @return array 31 | */ 32 | public function getMiddlewaresByAttributes(): array 33 | { 34 | $middlewares = []; 35 | 36 | /** @return string[] */ 37 | $filterArguments = function (array $arguments): array { 38 | $items = []; 39 | 40 | foreach ($arguments as $argument) { 41 | if (is_string($argument) && $argument !== '') $items[] = $argument; 42 | if ($argument instanceof BackedEnum && $argument->value !== '') $items[] = (string) $argument->value; 43 | } 44 | 45 | return $items; 46 | }; 47 | 48 | /** @var ReflectionAttribute[] $attributes */ 49 | $push = function (array $attributes, ?string $method = null) use (&$middlewares, $filterArguments) { 50 | foreach ($attributes as $attribute) { 51 | /** @var Middleware $middleware */ 52 | $middleware = $attribute->newInstance(); 53 | $arguments = []; 54 | 55 | if (!is_array($middleware->arguments)) { 56 | $middleware->arguments = [$middleware->arguments]; 57 | } 58 | 59 | foreach ($middleware->arguments as $argument) { 60 | $items = $filterArguments(is_array($argument) ? $argument : [$argument]); 61 | if ($items) { 62 | $arguments[] = implode('|', $items); 63 | } 64 | } 65 | 66 | $name = $middleware->name; 67 | if ($arguments) $name .= ':'.implode(',', $arguments); 68 | 69 | $middlewares[] = [ 70 | 'middleware' => $name, 71 | 'options' => &$middleware->options 72 | ]; 73 | 74 | $middlewareOptions = new ControllerMiddlewareOptions($middleware->options); 75 | 76 | if ($method) $middlewareOptions->only((array) $method); 77 | elseif ($middleware->only) $middlewareOptions->only((array) $middleware->only); 78 | elseif ($middleware->except) $middlewareOptions->except((array) $middleware->except); 79 | } 80 | }; 81 | 82 | $class = new ReflectionClass($this); 83 | 84 | // ClassMiddleware (Higher priority, before methods) 85 | $push($class->getAttributes(ClassMiddleware::class)); 86 | 87 | // Methods 88 | foreach ($class->getMethods() as $method) { 89 | $push($method->getAttributes(Middleware::class), $method->name); 90 | } 91 | 92 | // Class 93 | $push($class->getAttributes(Middleware::class)); 94 | 95 | return $middlewares; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/BaseController.php: -------------------------------------------------------------------------------- 1 | router = new Router(Mockery::mock(Dispatcher::class), Container::getInstance()); 24 | $this->router->get('resource', [FooController::class, 'index']); 25 | $this->router->post('resource', [FooController::class, 'store']); 26 | $this->router->put('resource/{id}', [FooController::class, 'update']); 27 | $this->router->delete('resource/{id}', [FooController::class, 'destroy'])->middleware('classic'); 28 | } catch (Exception) { 29 | } 30 | } 31 | 32 | public function testRouterInstance(): void 33 | { 34 | $this->assertNotNull($this->router); 35 | } 36 | 37 | public function testIndexMiddleware(): void 38 | { 39 | $route = $this->getRoute('GET'); 40 | 41 | $this->assertNotEquals(false, $route); 42 | $this->assertEquals(['one', 'two:arg1,arg2', 'three'], $route->gatherMiddleware()); 43 | } 44 | 45 | public function testStoreMiddleware(): void 46 | { 47 | $route = $this->getRoute('POST'); 48 | 49 | $this->assertNotEquals(false, $route); 50 | $this->assertEquals(['second', 'four', 'five:arg', 'six'], $route->gatherMiddleware()); 51 | } 52 | 53 | public function testUpdateMiddleware(): void 54 | { 55 | $route = $this->getRoute('PUT'); 56 | 57 | $this->assertNotEquals(false, $route); 58 | $this->assertEquals(['first', 'second', 'three', 'six'], $route->gatherMiddleware()); 59 | } 60 | 61 | public function testDestroyMiddleware(): void 62 | { 63 | $route = $this->getRoute('DELETE'); 64 | 65 | $this->assertNotEquals(false, $route); 66 | $this->assertEquals(['classic', 'multiple:arg1,arg2-1|arg2-2', 'three'], $route->gatherMiddleware()); 67 | } 68 | 69 | public function getRoute(string $method): Route | bool 70 | { 71 | $routes = $this->router->getRoutes()->get($method); 72 | return end($routes); 73 | } 74 | } 75 | --------------------------------------------------------------------------------