Skip to content

attributes#1

Closed
ljonesfl wants to merge 4 commits intodevelopfrom
feature/attributes
Closed

attributes#1
ljonesfl wants to merge 4 commits intodevelopfrom
feature/attributes

Conversation

@ljonesfl
Copy link
Member

@ljonesfl ljonesfl commented Dec 28, 2025

Summary by CodeRabbit

  • New Features

    • Attribute-based routing: per-method route attributes for GET/POST/PUT/DELETE, route groups with prefixes and merged filters, immutable route definitions, and a route discovery utility for controllers and directories.
  • Documentation

    • Comprehensive guide on attribute routing, usage examples, YAML-to-attributes migration, configuration, benefits, and performance notes.
  • Tests

    • Unit tests covering scanning, grouping, multiple routes per method, caching, directory scanning, and route metadata.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Adds attribute-based routing: a Route attribute and method-specific attributes (Get/Post/Put/Delete), class-level RouteGroup, RouteDefinition DTO, RouteScanner for reflection- and directory-based discovery with caching, README documentation, and comprehensive unit tests for scanner behavior.

Changes

Cohort / File(s) Summary
Documentation
README.md
New documentation describing attribute-based routing usage, HTTP method attributes, naming/filters, route groups, multiple routes per method, YAML+attributes hybrid, migration examples, and performance notes.
Route Attribute Base & HTTP Method Attributes
src/Routing/Attributes/Route.php, src/Routing/Attributes/Get.php, src/Routing/Attributes/Post.php, src/Routing/Attributes/Put.php, src/Routing/Attributes/Delete.php
Adds Route attribute (constructor-promoted readonly props: path, method, name, filters) and repeatable method-level attributes Get, Post, Put, Delete that call Route with normalized HTTP methods.
Route Group Management
src/Routing/Attributes/RouteGroup.php
Adds RouteGroup class-level attribute with readonly prefix and filters, plus applyPrefix() and mergeFilters() for path normalization and filter composition.
Route Discovery Infrastructure
src/Routing/RouteDefinition.php, src/Routing/RouteScanner.php
Adds immutable RouteDefinition DTO and RouteScanner that reflects controller classes, reads RouteGroup and Route attributes, applies prefixes/filters, supports scanning multiple classes and directories, memoizes results, and exposes cache clearing.
Unit Tests
tests/unit/RouteScannerTest.php
Adds extensive tests exercising RouteScanner: class/group scanning, multiple routes per method, filter merging, caching/clearCache behavior, directory scanning (including nested and eval-defined classes), and edge cases for prefix normalization.

Sequence Diagram

sequenceDiagram
    actor App as Application
    participant Scanner as RouteScanner
    participant Reflection as PHP Reflection
    participant Controller as Controller Class
    participant Group as RouteGroup
    participant RouteAttr as Route / Method Attributes
    participant Registry as RouteDefinition[] 

    App->>Scanner: scanClass("SomeController")
    activate Scanner

    Scanner->>Reflection: new ReflectionClass("SomeController")
    Reflection-->>Scanner: class metadata
    Scanner->>Controller: read class attributes
    alt RouteGroup present
        Controller-->>Scanner: RouteGroup instance
        Scanner->>Group: getPrefix(), getFilters()
        Group-->>Scanner: prefix, filters
    end

    Note over Scanner: iterate methods
    loop per method
        Scanner->>Reflection: getMethods()
        Reflection-->>Scanner: ReflectionMethod[]
        Scanner->>RouteAttr: get Route attributes
        RouteAttr-->>Scanner: Route instances
        alt group prefix exists
            Scanner->>Group: applyPrefix(routePath)
            Group-->>Scanner: combinedPath
        end
        alt group filters exist
            Scanner->>Group: mergeFilters(routeFilters)
            Group-->>Scanner: mergedFilters
        end
        Scanner->>Registry: append RouteDefinition(path, method, controller, action, name, filters)
    end

    Scanner-->>App: return RouteDefinition[]
    deactivate Scanner
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nibble paths where attributes bloom,

Methods hop out from each tidy room,
Get, Post, Put, Delete — a carrot-lined row,
Groups tuck prefixes where routes like to go,
A rabbit's cheer for routes now in bloom 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title "attributes" is too vague and generic, failing to convey the specific nature of the changes despite implementing a comprehensive attribute-based routing system. Consider a more descriptive title such as "Add attribute-based routing system" or "Implement PHP attributes for route definitions" that clearly indicates the main feature being introduced.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/attributes

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac52c6 and 08f6e47.

📒 Files selected for processing (1)
  • tests/unit/RouteScannerTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/RouteScannerTest.php

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 28, 2025

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/unit/RouteScannerTest.php (1)

41-142: Comprehensive test suite for RouteScanner.

Good coverage of:

  • Path composition with RouteGroup prefixes
  • Filter merging (group + method filters)
  • Route names
  • Multiple routes per method
  • Cache behavior and clearing
  • Non-existent class handling

The use of assertSame in testScanClassesCachesResults correctly verifies object identity for cache validation.

Consider adding tests for Put and Delete attributes to complete coverage of all HTTP method attributes, though the current tests adequately validate the pattern since all method attributes follow the same inheritance structure.

src/Routing/Attributes/RouteGroup.php (1)

65-79: Edge case: trailing slash when routePath is / or empty.

If routePath is / (or empty), the result will have a trailing slash (e.g., /admin/). This may cause route matching issues depending on how the router handles trailing slashes.

Consider trimming the trailing slash from the result:

🔎 Proposed fix
 public function applyPrefix( string $routePath ): string
 {
     if( $this->prefix === '' )
     {
         return $routePath;
     }

     // Ensure prefix starts with / and doesn't end with /
     $prefix = '/' . trim( $this->prefix, '/' );

     // Ensure route starts with /
     $path = '/' . ltrim( $routePath, '/' );

-    return $prefix . $path;
+    return rtrim( $prefix . $path, '/' ) ?: '/';
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca53b43 and 795976e.

📒 Files selected for processing (10)
  • README.md
  • src/Routing/Attributes/Delete.php
  • src/Routing/Attributes/Get.php
  • src/Routing/Attributes/Post.php
  • src/Routing/Attributes/Put.php
  • src/Routing/Attributes/Route.php
  • src/Routing/Attributes/RouteGroup.php
  • src/Routing/RouteDefinition.php
  • src/Routing/RouteScanner.php
  • tests/unit/RouteScannerTest.php
🧰 Additional context used
🧬 Code graph analysis (9)
src/Routing/Attributes/Delete.php (4)
src/Routing/Attributes/Get.php (2)
  • Attribute (21-37)
  • __construct (29-36)
src/Routing/Attributes/Post.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Put.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Route.php (2)
  • Attribute (23-72)
  • __construct (32-39)
src/Routing/RouteDefinition.php (6)
src/Routing/Attributes/Delete.php (1)
  • __construct (26-33)
src/Routing/Attributes/Get.php (1)
  • __construct (29-36)
src/Routing/Attributes/Post.php (1)
  • __construct (26-33)
src/Routing/Attributes/Put.php (1)
  • __construct (26-33)
src/Routing/Attributes/Route.php (1)
  • __construct (32-39)
src/Routing/Attributes/RouteGroup.php (1)
  • __construct (36-41)
src/Routing/Attributes/Get.php (4)
src/Routing/Attributes/Post.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Put.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Route.php (2)
  • Attribute (23-72)
  • __construct (32-39)
src/Routing/RouteDefinition.php (1)
  • __construct (22-31)
src/Routing/Attributes/RouteGroup.php (3)
src/Routing/Attributes/Route.php (3)
  • Attribute (23-72)
  • __construct (32-39)
  • getFilters (68-71)
tests/unit/RouteScannerTest.php (1)
  • RouteGroup (12-23)
src/Routing/RouteDefinition.php (1)
  • __construct (22-31)
tests/unit/RouteScannerTest.php (2)
src/Routing/RouteScanner.php (4)
  • RouteScanner (18-191)
  • scanClass (29-64)
  • clearCache (187-190)
  • scanClasses (73-84)
src/Routing/RouteDefinition.php (1)
  • getControllerMethod (38-41)
src/Routing/Attributes/Put.php (5)
src/Routing/Attributes/Delete.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Get.php (2)
  • Attribute (21-37)
  • __construct (29-36)
src/Routing/Attributes/Post.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Route.php (2)
  • Attribute (23-72)
  • __construct (32-39)
src/Routing/Route.php (1)
  • Route (8-64)
src/Routing/Attributes/Post.php (2)
src/Routing/Attributes/Get.php (2)
  • Attribute (21-37)
  • __construct (29-36)
src/Routing/Attributes/Route.php (2)
  • Attribute (23-72)
  • __construct (32-39)
src/Routing/RouteScanner.php (4)
tests/unit/RouteScannerTest.php (1)
  • RouteGroup (12-23)
src/Routing/Attributes/Route.php (4)
  • getPath (44-47)
  • getFilters (68-71)
  • getMethod (52-55)
  • getName (60-63)
src/Routing/Attributes/RouteGroup.php (3)
  • getFilters (54-57)
  • applyPrefix (65-79)
  • mergeFilters (87-90)
src/Routing/RouteDefinition.php (1)
  • RouteDefinition (12-42)
src/Routing/Attributes/Route.php (6)
src/Routing/Attributes/Delete.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Get.php (2)
  • Attribute (21-37)
  • __construct (29-36)
src/Routing/Attributes/Post.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/Put.php (2)
  • Attribute (18-34)
  • __construct (26-33)
src/Routing/Attributes/RouteGroup.php (3)
  • Attribute (29-91)
  • __construct (36-41)
  • getFilters (54-57)
src/Routing/RouteDefinition.php (1)
  • __construct (22-31)
🪛 markdownlint-cli2 (0.18.1)
README.md

92-92: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


107-107: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


134-134: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


146-146: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


164-164: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


179-179: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


218-218: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


227-227: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

🔇 Additional comments (11)
README.md (1)

84-236: Well-structured documentation for the new attribute-based routing feature.

The documentation is comprehensive, covering usage patterns, configuration, migration path, and performance considerations. The examples align with the actual implementation in the codebase.

The static analysis hints about code block style (fenced vs indented) can be safely ignored—fenced code blocks are more readable for these multi-line examples and are consistent with the existing documentation style in this file.

src/Routing/Attributes/Delete.php (1)

1-34: LGTM!

The Delete attribute follows the same pattern as the other HTTP method attributes (Get, Post, Put), properly extending Route and delegating to the parent constructor with the correct HTTP method.

src/Routing/Attributes/Get.php (1)

1-37: LGTM!

The Get attribute is well-implemented with clear documentation and usage examples. The repeatable attribute pattern allows multiple routes on a single method, which is a useful feature for API versioning.

src/Routing/Attributes/Put.php (1)

1-34: LGTM!

The Put attribute is implemented consistently with the other HTTP method attributes.

src/Routing/RouteDefinition.php (1)

1-42: Clean immutable data class.

The use of constructor property promotion with readonly is idiomatic PHP 8.1+. The getControllerMethod() helper provides a convenient format for route registration.

src/Routing/Attributes/Post.php (1)

1-34: LGTM!

The Post attribute follows the established pattern consistently.

src/Routing/Attributes/Route.php (1)

23-72: Well-designed base attribute class.

The Route class provides a clean foundation for HTTP method attributes. The getMethod() normalization to uppercase ensures consistency. The readonly properties with getter methods provide good encapsulation while maintaining immutability.

Note: The class accepts any HTTP method string, which provides flexibility for adding PATCH, OPTIONS, or HEAD attributes in the future without modifying the base class.

tests/unit/RouteScannerTest.php (1)

11-39: Test controllers are well-structured for validating RouteScanner behavior.

The inline test controllers provide good coverage for:

  • RouteGroup with prefix and filters
  • Controllers without RouteGroup
  • Multiple routes on a single method

These cover the key scenarios in the attribute-based routing system.

src/Routing/Attributes/RouteGroup.php (1)

29-41: Clean use of PHP 8 promoted properties.

The attribute definition and constructor leverage modern PHP 8 features appropriately with readonly promoted properties and sensible defaults.

src/Routing/RouteScanner.php (2)

29-64: Well-structured class scanning with caching.

Good defensive programming with the class_exists check and effective caching to prevent redundant scanning. The implementation correctly handles class-level RouteGroup attributes and iterates only over public methods.


113-150: Correct use of IS_INSTANCEOF for polymorphic attribute matching.

The use of \ReflectionAttribute::IS_INSTANCEOF correctly captures all Route subclasses (Get, Post, Put, Delete), and the RouteGroup integration properly applies prefixes and merges filters.

ljonesfl and others added 3 commits December 27, 2025 20:22
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@ljonesfl ljonesfl closed this Dec 28, 2025
@ljonesfl ljonesfl deleted the feature/attributes branch December 28, 2025 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant