Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
.phpunit.result.cache
composer.lock
Taskfile.yml
.DS_Store
36 changes: 36 additions & 0 deletions artisan
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env php
<?php

// THIS IS NOT A REAL ARTISAN FILE
// This file is used for development and testing purposes only.

ini_set('display_errors', 1);
error_reporting(E_ALL);

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\Route;
use Orchestra\Testbench\Foundation\Application;

require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/src/helpers.php';

// Create the App using the Testbench factory
$appFactory = Application::make(__DIR__);

// Don't actually configure the app here, testbench will override it
// $appFactory->configure([]);

// Create the Laravel app instance
$app = $appFactory->create();

// We need to register the service provider manually
$app->register(RouteDocs\RouteDocsServiceProvider::class);

Route::middleware('web')
->group('examples/routes.php');

// Do it
$app->make(Kernel::class)->handle(
$input = new Symfony\Component\Console\Input\ArgvInput(),
new Symfony\Component\Console\Output\ConsoleOutput()
);
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"autoload-dev": {
"psr-4": {
"Examples\\Http\\Controllers\\": "examples/",
"Examples\\Http\\": "examples/",
"Tests\\": "tests/"
}
},
Expand Down
40 changes: 0 additions & 40 deletions examples/BookingController.php

This file was deleted.

52 changes: 52 additions & 0 deletions examples/Controllers/BookingController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Examples\Http\Controllers;

use Illuminate\Http\Request;
use RouteDocs\Attributes\delete;
use RouteDocs\Attributes\formParam;
use RouteDocs\Attributes\get;
use RouteDocs\Attributes\param;
use RouteDocs\Attributes\post;
use RouteDocs\Attributes\queryParam;

class BookingController
{
#[get(path: '/bookings', name: 'bookings.index')]
#[queryParam(key: 'dateStart', cast: 'date', description: 'Filter bookings by date')]
#[queryParam(key: 'dateEnd', cast: 'date', description: 'Filter bookings by date')]
//#[returns(Booking::class)] coming soon
public function index()
{
// Return a list of bookings
}

#[post('/bookings', name: 'bookings.store')]
#[formParam('dateStart', 'date', required: false, description: 'Start date for the booking')]
#[formParam('dateEnd', 'date', required: false, description: 'End date for the booking')]
public function store(Request $request)
{
// Validate and create a new booking
}

#[get('/bookings/{id}', name: 'bookings.show')]
#[param('path', key: 'id', cast: 'int', required: true, description: 'Booking ID')]
#[param('query', key: 'hydrate', cast: 'bool', required: false, description: 'Hydrate the booking details')]
public function show(int $id)
{
// Return details for a single booking
}

#[delete(path: '/bookings/{id}/cancel', name: 'bookings.cancel')]
public function cancel(int $id)
{
// Cancel a booking
}

#[get(path: '/bookings/stats/{frequency}', name: 'bookings.stats')]
#[param('path', key: 'frequency', cast: 'string', required: true, description: 'Frequency of the stats (daily, weekly, monthly)', example: 'daily|weekly|monthly')]
public function stats()
{
// Return daily booking statistics
}
}
41 changes: 41 additions & 0 deletions examples/Controllers/PostApiController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Examples\Http\Controllers;

use RouteDocs\Attributes\delete;
use RouteDocs\Attributes\get;
use RouteDocs\Attributes\post;
use RouteDocs\Attributes\put;

class PostApiController
{
#[get('/api/posts', name: 'posts.index')]
public function index(): string
{
return 'Welcome to the Post API Controller!';
}

#[post('/api/posts', name: 'posts.store')]
public function store(): string
{
return 'Store a new post';
}

#[get('/api/posts/{post}', name: 'posts.show')]
public function show(int $post): string
{
return "Show post with ID: $post";
}

#[put('/api/posts/{post}', name: 'posts.update')]
public function update(int $post): string
{
return "Update post with ID: $post";
}

#[delete('/api/posts/{post}', name: 'posts.destroy')]
public function destroy(int $post): string
{
return "Delete post with ID: $post";
}
}
53 changes: 53 additions & 0 deletions examples/Controllers/PostController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Examples\Http\Controllers;

use RouteDocs\Attributes\delete;
use RouteDocs\Attributes\get;
use RouteDocs\Attributes\post;
use RouteDocs\Attributes\put;

class PostController
{
#[get('/posts', name: 'posts.index')]
public function index(): string
{
return 'Welcome to the Post Controller!';
}

#[post('/posts', name: 'posts.store')]
public function store(): string
{
return 'Store a new post';
}

#[get('/posts/create', name: 'posts.create')]
public function create(): string
{
return 'Create a new post';
}

#[get('/posts/{post}', name: 'posts.show')]
public function show(int $post): string
{
return "Show post with ID: $post";
}

#[get('/posts/{post}/edit', name: 'posts.edit')]
public function edit(int $post): string
{
return "Edit post with ID: $post";
}

#[put('/posts/{post}', name: 'posts.update')]
public function update(int $post): string
{
return "Update post with ID: $post";
}

#[delete('/posts/{post}', name: 'posts.destroy')]
public function destroy(int $post): string
{
return "Delete post with ID: $post";
}
}
19 changes: 19 additions & 0 deletions src/Attributes/formParam.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace RouteDocs\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class formParam extends param
{
public function __construct(
string $key,
?string $cast = null,
bool $required = false,
?string $description = '',
?string $example = null,
) {
parent::__construct('form', $key, $cast, $required, $description, $example);
}
}
19 changes: 19 additions & 0 deletions src/Attributes/param.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace RouteDocs\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class param
{
public function __construct(
public string|object $type,
public string $key,
public ?string $cast = null,
public bool $required = false,
public ?string $description = '',
public ?string $example = null,
) {
}
}
19 changes: 19 additions & 0 deletions src/Attributes/queryParam.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace RouteDocs\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class queryParam extends param
{
public function __construct(
string $key,
?string $cast = null,
bool $required = false,
?string $description = '',
?string $example = null,
) {
parent::__construct('query', $key, $cast, $required, $description, $example);
}
}
31 changes: 26 additions & 5 deletions src/Console/ListEndpoints.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,37 @@

class ListEndpoints extends Command
{
protected $signature = 'route:docs {--sort=path : Sort by "path", "name", or "controller"}';
protected $signature = 'route:docs' .
' {--sort=path : Sort by "path", "name", or "controller"}' .
' {--json : Output in JSON format}' .
' {--path= : Path to controller directory}';
protected $description = 'List all endpoints defined using HTTP method attributes';

public function handle(): int
protected RouteDocInspector $inspector;

public function __construct(RouteDocInspector $inspector)
{
$inspector = new RouteDocInspector();
$routes = $inspector->getDocumentedRoutes();
parent::__construct();
$this->inspector = $inspector;
}

public function handle(): int
{
$sortKey = $this->option('sort') ?? 'path';
$sorted = $routes->sortByKey($sortKey);
$path = $this->option('path');

if ($path) {
$this->inspector = new RouteDocInspector($path);

Check warning on line 30 in src/Console/ListEndpoints.php

View check run for this annotation

Codecov / codecov/patch

src/Console/ListEndpoints.php#L30

Added line #L30 was not covered by tests
}

$routes = $this->inspector->getDocumentedRoutes();
$sorted = $routes->sortByKey($sortKey);

if ($this->option('json')) {
$this->line($sorted->toJson());

return Command::SUCCESS;
}

$hasErrors = $sorted->hasErrors();
$headers = $hasErrors
Expand Down
4 changes: 2 additions & 2 deletions src/Console/ValidateEndpoints.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ class ValidateEndpoints extends Command
protected $signature = 'route:docs:validate {--path= : Path to controller directory}';
protected $description = 'Validate route attribute usage across controllers.';

protected RouteDocInspector $inspector;

public function __construct(RouteDocInspector $inspector)
{
// This allows both CLI flexibility and test mocking
parent::__construct();
$this->inspector = $inspector;
}

public function handle(): int
{
// If --path is provided, re-instantiate inspector with path
if ($path = $this->option('path')) {
$this->inspector = new RouteDocInspector($path);
}
Expand Down
Loading