Skip to content

Add array and ArrayObject support for Input attribute#1

Merged
koriym merged 9 commits into1.xfrom
array
Jul 3, 2025
Merged

Add array and ArrayObject support for Input attribute#1
koriym merged 9 commits into1.xfrom
array

Conversation

@koriym
Copy link
Member

@koriym koriym commented Jul 3, 2025

Summary

Add support for array and ArrayObject parameters to the Input attribute system, allowing automatic conversion of array data to collections of Input objects.

Features

  • Array Support: Use #[Input(item: UserInput::class)] with array type parameters
  • ArrayObject Support: Use #[Input(item: UserInput::class)] with ArrayObject type parameters
  • ArrayObject Inheritance: Support for custom ArrayObject subclasses
  • Type Safety: Full PHPStan and Psalm compatibility with proper type annotations
  • Edge Case Handling: Robust handling of non-array data and invalid array elements

Example Usage

final class UserInput
{
    public function __construct(
        #[Input] public readonly string $id,
        #[Input] public readonly string $name
    ) {}
}

final class UserController
{
    public function updateUsers(
        #[Input(item: UserInput::class)] array $users
    ) {
        foreach ($users as $user) {
            echo $user->name; // Each element is a UserInput instance
        }
    }
}

Query Data Format

$data = [
    'users' => [
        ['id' => '1', 'name' => 'John'],
        ['id' => '2', 'name' => 'Jane']
    ]
];

Test Coverage

  • ✅ 100% code coverage maintained
  • ✅ Array parameter handling
  • ✅ ArrayObject parameter handling
  • ✅ Custom ArrayObject inheritance
  • ✅ Edge cases (empty arrays, non-array data, invalid elements)
  • ✅ Static analysis (PHPStan + Psalm) passes

Documentation

  • Updated README.md with comprehensive array support documentation
  • Added examples for basic usage and ArrayObject inheritance
  • Documented query data format requirements

🤖 Generated with Claude Code

Summary by Sourcery

Add support for mapping query arrays and ArrayObject instances into collections of Input-decorated objects by introducing an item parameter on the Input attribute and updating the parsing logic accordingly.

New Features:

  • Support array parameters with #[Input(item: …)] to produce collections of Input objects
  • Support ArrayObject parameters (including subclasses) with #[Input(item: …)] to produce collections of Input objects

Enhancements:

  • Introduce createArrayOfInputs helper and extend resolveParameter to handle arrays and ArrayObject inheritance
  • Extend Input attribute to accept an optional item class parameter for collection typing

Documentation:

  • Update README with array and ArrayObject support documentation and usage examples

Tests:

  • Add comprehensive tests for array inputs, ArrayObject inputs, empty/missing/invalid data cases, and custom ArrayObject subclasses

Chores:

  • Add demo script showcasing array and ArrayObject usage

Summary by CodeRabbit

  • New Features

    • Added support for automatically mapping arrays and ArrayObject collections of input objects using the item parameter in the input attribute.
    • Enhanced input handling to support custom ArrayObject subclasses for input collections.
    • Introduced new demo and documentation examples illustrating array and collection input scenarios.
    • Extended allowed Bash command permissions to include PHPUnit and PHP Mess Detector commands.
  • Documentation

    • Expanded the README with a new section explaining array and ArrayObject input support, usage examples, and expected input formats.
  • Tests

    • Added comprehensive tests for array and collection input handling, including edge cases and custom collection classes.

koriym and others added 4 commits July 3, 2025 18:52
- Document array and ArrayObject support with item parameter
- Include examples for basic array usage and custom ArrayObject inheritance
- Show query data format for arrays
- Demonstrate controller method usage with array parameters

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jul 3, 2025

Reviewer's Guide

This PR extends the Input attribute system to automatically convert array and ArrayObject parameters into collections of Input-annotated objects by adding item-type handling in InputQuery, updating the Input attribute, documentation, tests, and a demo script.

Sequence diagram for array and ArrayObject parameter resolution in InputQuery

sequenceDiagram
    participant Controller as UserController
    participant InputQuery
    participant Input as Input attribute
    participant UserInput
    participant ArrayObject
    Controller->>InputQuery: getArguments(method, query)
    InputQuery->>Input: read item type (UserInput::class)
    alt array parameter
        InputQuery->>InputQuery: createArrayOfInputs(paramName, query, UserInput)
        loop for each user data
            InputQuery->>UserInput: create(UserInput, itemData)
        end
        InputQuery-->>Controller: array of UserInput
    else ArrayObject parameter
        InputQuery->>InputQuery: createArrayOfInputs(paramName, query, UserInput)
        loop for each user data
            InputQuery->>UserInput: create(UserInput, itemData)
        end
        InputQuery->>ArrayObject: new ArrayObject(array of UserInput)
        InputQuery-->>Controller: ArrayObject of UserInput
    end
Loading

Entity relationship diagram for UserInput and UserCollection

erDiagram
    USER_INPUT {
        string id
        string name
    }
    USER_COLLECTION ||--o{ USER_INPUT : contains
Loading

Class diagram for updated Input attribute and InputQuery handling

classDiagram
    class Input {
        +string|null item
        +__construct(string|null item = null)
    }
    class InputQuery {
        -createArrayOfInputs(paramName: string, query: array, itemClass: string): array
        -resolveParameter(param: ReflectionParameter, query: array): mixed
        ...
    }
    class ArrayObject
    class UserInput {
        +string id
        +string name
        +__construct(string id, string name)
    }
    class UserCollection {
        +getFirst(): ?UserInput
    }
    UserCollection --|> ArrayObject
    InputQuery --> Input : uses
    InputQuery --> ArrayObject : instantiates
    InputQuery --> UserInput : creates
    InputQuery --> UserCollection : creates
    UserCollection --> UserInput : contains
    UserInput <.. Input : attribute
Loading

Class diagram for custom ArrayObject subclass support

classDiagram
    class UserCollection {
        +getFirst(): ?UserInput
    }
    class ArrayObject
    UserCollection --|> ArrayObject
    UserCollection --> UserInput : contains
Loading

File-Level Changes

Change Details Files
Extended InputQuery to handle array and ArrayObject parameters with item type
  • Imported ArrayObject, is_array, and is_subclass_of utilities
  • Added array type branch in resolveParameter to invoke createArrayOfInputs when Input::item is set
  • Added ArrayObject and subclass handling in resolveParameter to wrap created arrays
  • Implemented private createArrayOfInputs method for instantiating per-item Input objects
src/InputQuery.php
Enhanced Input attribute to declare item classes
  • Added readonly $item constructor parameter to Input attribute
src/Attribute/Input.php
Updated README with array and ArrayObject support documentation
  • Added usage examples for array, ArrayObject, and custom subclass scenarios
  • Documented query data format and inheritance support
README.md
Added tests covering new array and ArrayObject behaviors
  • Created ArrayInputTest for arrays, ArrayObject, edge cases, and custom subclass
  • Added fake helper classes UserInputWithAttribute and CustomArrayObject
tests/ArrayInputTest.php
tests/Fake/UserInputWithAttribute.php
tests/Fake/CustomArrayObject.php
Provided a demo script illustrating the new features
  • Added demo/ArrayDemo.php showcasing array and ArrayObject usage with InputQuery
demo/ArrayDemo.php

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Jul 3, 2025

Walkthrough

Array and collection input support was added to the input mapping system, enabling automatic population of method parameters typed as arrays or ArrayObject subclasses using the item parameter in the Input attribute. Documentation, demos, and extensive tests were introduced to illustrate and verify this new functionality. Additionally, Bash command permissions were extended to allow PHPUnit and PHP Mess Detector executions.

Changes

File(s) Change Summary
.claude/settings.local.json Allowed Bash execution of ./vendor/bin/phpunit:*, vendor/bin/phpunit:*, ./vendor/bin/phpmd:*, and a PHPUnit coverage command.
README.md Added documentation for array and ArrayObject input support with examples.
demo/ArrayDemo.php New demo script showing array and ArrayObject input mapping in action.
src/Attribute/Input.php Added constructor with optional item parameter to Input attribute.
src/InputQuery.php Implemented array and ArrayObject input mapping; added createArrayOfInputs and related private methods.
tests/ArrayInputTest.php New PHPUnit test class covering array/collection input scenarios and edge cases.
tests/Fake/CustomArrayObject.php Added custom ArrayObject subclass with getFirst() method for testing.
tests/Fake/UserInputWithAttribute.php Added input class with properties annotated by Input attribute for array input tests.
tests/Fake/UserInput.php Reformatted constructor body (no functional change).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant InputQuery
    participant Controller
    participant UserInput

    Client->>InputQuery: getArguments(reflection, query)
    InputQuery->>InputQuery: Detect array/ArrayObject param with #[Input(item: UserInput::class)]
    InputQuery->>InputQuery: createArrayOfInputs(paramName, query, UserInput::class)
    loop For each element in query[paramName]
        InputQuery->>UserInput: create instance from element
    end
    InputQuery->>Controller: Provide array/ArrayObject of UserInput
    Controller->>Client: Method executes with populated collection
Loading

Poem

In fields of arrays, the rabbits now play,
With objects in rows, they hop all the way.
Collections and tests, a new demo in tow—
InputQuery grows smarter, just watch it go!
Arrays or objects, it’s all quite a treat,
As code and collections together now meet.
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10756d1 and 3e298aa.

📒 Files selected for processing (2)
  • .claude/settings.local.json (1 hunks)
  • tests/ArrayInputTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .claude/settings.local.json
  • tests/ArrayInputTest.php
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build (ubuntu-latest, 8.3)
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @koriym - I've reviewed your changes - here's some feedback:

  • You can consolidate the three separate branches for array, ArrayObject, and its subclasses into a single code path (e.g. unify logic based on whether the type is iterable) to reduce duplication in resolveParameter.
  • Instead of silently returning an empty array when the query value isn’t an array, consider throwing or logging an error for invalid data to help callers detect mis-typed input early.
  • Update the @return docblock on createArrayOfInputs to array<array-key, T> (or array<int|string, T>) so static analyzers and readers know you preserve the original keys.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- You can consolidate the three separate branches for `array`, `ArrayObject`, and its subclasses into a single code path (e.g. unify logic based on whether the type is iterable) to reduce duplication in `resolveParameter`.
- Instead of silently returning an empty array when the query value isn’t an array, consider throwing or logging an error for invalid data to help callers detect mis-typed input early.
- Update the `@return` docblock on `createArrayOfInputs` to `array<array-key, T>` (or `array<int|string, T>`) so static analyzers and readers know you preserve the original keys.

## Individual Comments

### Comment 1
<location> `src/InputQuery.php:315` </location>
<code_context>
+
+        $result = [];
+        /** @var mixed $itemData */
+        foreach ($arrayData as $key => $itemData) {
+            if (is_array($itemData)) {
+                // Query parameters from HTTP requests have string keys
+                /** @psalm-var array<string, mixed> $itemData */
+                /** @phpstan-var array<string, mixed> $itemData */
+                $result[$key] = $this->create($itemClass, $itemData);
+            }
+        }
+
</code_context>

<issue_to_address>
Non-array items in the input array are silently ignored.

This may cause data loss. Please handle or report non-array elements explicitly.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        $result = [];
        /** @var mixed $itemData */
        foreach ($arrayData as $key => $itemData) {
            if (is_array($itemData)) {
                // Query parameters from HTTP requests have string keys
                /** @psalm-var array<string, mixed> $itemData */
                /** @phpstan-var array<string, mixed> $itemData */
                $result[$key] = $this->create($itemClass, $itemData);
            }
        }
=======
        $result = [];
        /** @var mixed $itemData */
        foreach ($arrayData as $key => $itemData) {
            if (is_array($itemData)) {
                // Query parameters from HTTP requests have string keys
                /** @psalm-var array<string, mixed> $itemData */
                /** @phpstan-var array<string, mixed> $itemData */
                $result[$key] = $this->create($itemClass, $itemData);
            } else {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected array for item at key "%s", got %s.',
                        $key,
                        gettype($itemData)
                    )
                );
            }
        }
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

@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: 0

🔭 Outside diff range comments (1)
src/InputQuery.php (1)

88-169: Address the high complexity in resolveParameter() method.

The method has grown significantly complex with an NPath complexity of 384 (threshold is 200), making it harder to maintain and test. The logic for handling ArrayObject is also duplicated.

Consider extracting the array/collection handling logic into separate methods:

private function resolveParameter(ReflectionParameter $param, array $query): mixed
{
    $inputAttributes = $param->getAttributes(Input::class);
    $hasInputAttribute = ! empty($inputAttributes);

    if (! $hasInputAttribute) {
        return $this->resolveFromDI($param);
    }

    $type = $param->getType();
    $paramName = $param->getName();

    if (! $type instanceof ReflectionNamedType) {
        return $query[$paramName] ?? $this->getDefaultValue($param);
    }

+   // Handle collection types (arrays and ArrayObject)
+   if ($this->isCollectionType($type)) {
+       return $this->resolveCollectionParameter($param, $query, $inputAttributes[0]);
+   }

    if ($type->isBuiltin()) {
-       // Check if it's an array type with item specification
-       if ($type->getName() === 'array') {
-           $inputAttribute = $inputAttributes[0]->newInstance();
-           if ($inputAttribute->item !== null) {
-               assert(class_exists($inputAttribute->item));
-               $itemClass = $inputAttribute->item;
-               /** @var class-string<T> $itemClass */
-               return $this->createArrayOfInputs($paramName, $query, $itemClass);
-           }
-       }
        // Scalar type with #[Input]
        $value = $query[$paramName] ?? $this->getDefaultValue($param);
        return $this->convertScalar($value, $type);
    }

-   // Check if it's ArrayObject or its subclass with item specification
-   $className = $type->getName();
-   if (class_exists($className) && is_subclass_of($className, ArrayObject::class)) {
-       $inputAttribute = $inputAttributes[0]->newInstance();
-       if ($inputAttribute->item !== null) {
-           assert(class_exists($inputAttribute->item));
-           /** @var class-string<T> $itemClass */
-           $itemClass = $inputAttribute->item;
-           $array = $this->createArrayOfInputs($paramName, $query, $itemClass);
-           $reflectionClass = new ReflectionClass($className);
-           return $reflectionClass->newInstance($array);
-       }
-   }
-   
-   // Check if it's ArrayObject itself with item specification
-   if ($className === ArrayObject::class) {
-       $inputAttribute = $inputAttributes[0]->newInstance();
-       if ($inputAttribute->item !== null) {
-           assert(class_exists($inputAttribute->item));
-           /** @var class-string<T> $itemClass */
-           $itemClass = $inputAttribute->item;
-           $array = $this->createArrayOfInputs($paramName, $query, $itemClass);
-           return new ArrayObject($array);
-       }
-   }

    // Object type with #[Input] - create nested
    // ... existing logic
}

+private function isCollectionType(ReflectionNamedType $type): bool
+{
+    $typeName = $type->getName();
+    return $typeName === 'array' || 
+           ($typeName === ArrayObject::class) || 
+           (class_exists($typeName) && is_subclass_of($typeName, ArrayObject::class));
+}

+private function resolveCollectionParameter(ReflectionParameter $param, array $query, ReflectionAttribute $inputAttribute): mixed
+{
+    $type = $param->getType();
+    $paramName = $param->getName();
+    $inputAttr = $inputAttribute->newInstance();
+    
+    if ($inputAttr->item === null) {
+        return $query[$paramName] ?? $this->getDefaultValue($param);
+    }
+    
+    assert(class_exists($inputAttr->item));
+    /** @var class-string<T> $itemClass */
+    $itemClass = $inputAttr->item;
+    $array = $this->createArrayOfInputs($paramName, $query, $itemClass);
+    
+    $typeName = $type->getName();
+    if ($typeName === 'array') {
+        return $array;
+    }
+    
+    // ArrayObject or its subclass
+    if ($typeName === ArrayObject::class) {
+        return new ArrayObject($array);
+    }
+    
+    $reflectionClass = new ReflectionClass($typeName);
+    return $reflectionClass->newInstance($array);
+}
🧹 Nitpick comments (1)
tests/ArrayInputTest.php (1)

35-35: Consider adding documentation annotations to anonymous class methods.

While not critical, adding @param and @return annotations would improve static analysis compliance.

$controller = new class {
+   /**
+    * @param array<UserInputWithAttribute> $users
+    * @return array<UserInputWithAttribute>
+    */
    public function listUsers(
        #[Input(item: UserInputWithAttribute::class)]
        array $users,
    ): array {
        return $users;
    }
};

Also applies to: 68-68, 96-96, 116-116, 136-136, 164-164, 192-192

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03fc745 and 6a2f00d.

📒 Files selected for processing (9)
  • .claude/settings.local.json (1 hunks)
  • README.md (1 hunks)
  • demo/ArrayDemo.php (1 hunks)
  • src/Attribute/Input.php (1 hunks)
  • src/InputQuery.php (4 hunks)
  • tests/ArrayInputTest.php (1 hunks)
  • tests/Fake/CustomArrayObject.php (1 hunks)
  • tests/Fake/UserInput.php (1 hunks)
  • tests/Fake/UserInputWithAttribute.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
tests/Fake/UserInputWithAttribute.php (2)
src/InputQuery.php (2)
  • InputQuery (44-327)
  • __construct (46-49)
demo/ArrayDemo.php (1)
  • __construct (14-20)
tests/Fake/CustomArrayObject.php (1)
src/InputQuery.php (1)
  • InputQuery (44-327)
tests/ArrayInputTest.php (5)
src/InputQuery.php (1)
  • InputQuery (44-327)
tests/Fake/CustomArrayObject.php (2)
  • CustomArrayObject (9-15)
  • getFirst (11-14)
tests/Fake/UserInputWithAttribute.php (1)
  • UserInputWithAttribute (9-18)
demo/ArrayDemo.php (1)
  • listUsers (26-34)
src/InputQueryInterface.php (1)
  • getArguments (19-19)
demo/ArrayDemo.php (4)
src/InputQuery.php (2)
  • InputQuery (44-327)
  • __construct (46-49)
tests/Fake/UserInputWithAttribute.php (1)
  • __construct (11-17)
tests/ArrayInputTest.php (7)
  • listUsers (35-40)
  • listUsers (68-73)
  • listUsers (96-101)
  • listUsers (116-121)
  • listUsers (136-141)
  • listUsers (164-169)
  • listUsers (192-197)
src/InputQueryInterface.php (1)
  • getArguments (19-19)
🪛 GitHub Actions: Static Analysis
src/InputQuery.php

[error] 88-88: PHP Mess Detector: The method resolveParameter() has an NPath complexity of 384. The configured NPath complexity threshold is 200.

🪛 GitHub Check: Coding Standards
tests/ArrayInputTest.php

[failure] 164-164:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 164-164:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 136-136:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 136-136:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 116-116:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 116-116:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 96-96:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 96-96:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 35-35:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 35-35:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.

🔇 Additional comments (17)
.claude/settings.local.json (1)

7-7: LGTM: Necessary permission for new test infrastructure.

The addition of PHPUnit execution permission aligns with the new test files introduced in this PR and follows the existing permission pattern.

tests/Fake/UserInput.php (1)

14-15: LGTM: Formatting improvement.

The formatting change from inline to multi-line constructor body improves code consistency.

tests/Fake/CustomArrayObject.php (1)

9-15: LGTM: Well-designed test helper class.

The CustomArrayObject class provides a clean test helper for verifying custom ArrayObject subclass support. The getFirst() method implementation using array access with null coalescing is correct and the mixed return type is appropriate.

src/Attribute/Input.php (1)

12-15: LGTM: Core enhancement for array support.

The constructor addition with the optional item parameter successfully enables array and ArrayObject support while maintaining backward compatibility. The string|null type with default null is appropriate for specifying item class names.

tests/Fake/UserInputWithAttribute.php (1)

11-17: LGTM: Well-structured test input class.

The UserInputWithAttribute class follows the established pattern for input classes with proper #[Input] attribute annotations on constructor parameters. The structure is ideal for testing the new array input functionality.

README.md (1)

123-189: Excellent documentation of the new array support feature!

The new "Array Support" section is comprehensive and well-structured. It clearly explains:

  • Usage of the #[Input(item: ...)] attribute for arrays and ArrayObject collections
  • Expected query data format with practical examples
  • Support for custom ArrayObject subclasses
  • Integration with the existing input system

The examples are clear and demonstrate both basic and advanced usage scenarios.

src/InputQuery.php (2)

7-7: Good addition of necessary imports.

The new imports for ArrayObject support are appropriate and necessary for the enhanced functionality.

Also applies to: 20-20, 23-23, 30-30


294-325: Well-implemented createArrayOfInputs method.

The method correctly handles:

  • Missing parameters (returns empty array)
  • Non-array values (returns empty array)
  • Filters out non-array elements
  • Maintains array keys for valid elements
  • Proper type annotations for static analysis

The implementation is robust and handles edge cases appropriately.

tests/ArrayInputTest.php (6)

1-24: Excellent test setup and imports.

The test class is well-structured with proper setup using dependency injection and all necessary imports.


25-56: Comprehensive test for array input functionality.

The test correctly verifies:

  • Array parameter resolution with #[Input(item: ...)]
  • Proper instantiation of UserInputWithAttribute objects
  • Correct data mapping from query to object properties
  • Array structure preservation

58-89: Good coverage of ArrayObject functionality.

The test validates ArrayObject parameter handling with proper type checking and data verification.


91-109: Excellent edge case coverage.

These tests properly handle:

  • Empty arrays
  • Missing parameters
  • Non-array values

The implementation correctly returns empty arrays for these edge cases, which is the expected behavior.

Also applies to: 111-129, 131-149


151-180: Robust handling of mixed array elements.

The test demonstrates that the implementation correctly:

  • Filters out non-array elements
  • Preserves array keys for valid elements
  • Only processes valid array elements into objects

This is exactly the right behavior for handling potentially malformed input data.


182-218: Great test for custom ArrayObject subclasses.

The test validates:

  • Custom ArrayObject subclass instantiation
  • Preservation of custom methods (getFirst())
  • Proper inheritance behavior

This confirms the implementation works with ArrayObject inheritance as documented.

demo/ArrayDemo.php (3)

12-21: Well-defined input class.

The User class correctly demonstrates the pattern of using #[Input] attributes on constructor parameters for input mapping.


24-45: Excellent demonstration of array and ArrayObject usage.

The controller methods clearly show:

  • Array parameter handling with #[Input(item: User::class)]
  • ArrayObject parameter handling with the same pattern
  • Proper iteration and access to typed objects
  • Clear output for demonstration purposes

47-70: Effective demonstration of the complete workflow.

The demo successfully shows:

  • InputQuery setup with dependency injection
  • Sample query data structure (matching the documented format)
  • Method resolution using reflection
  • Argument resolution and method invocation
  • Complete end-to-end usage of the new functionality

This serves as an excellent reference for users implementing the feature.

koriym and others added 3 commits July 3, 2025 20:14
Split complex resolveParameter method into smaller, focused methods:
- resolveInputParameter: handles Input attribute processing
- resolveBuiltinType: handles array and scalar types
- resolveObjectType: handles object types and ArrayObject
- resolveArrayObjectType: handles ArrayObject creation logic

This reduces NPath complexity from 384 to below the 200 threshold
while maintaining the same functionality and test coverage.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@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: 0

🧹 Nitpick comments (3)
tests/ArrayInputTest.php (3)

26-57: Consider adding PHPDoc annotations to satisfy static analysis.

The test logic is correct. To address the static analysis warnings, you could add PHPDoc annotations to the anonymous class method:

         $controller = new class {
+            /**
+             * @param array<int, UserInputWithAttribute> $users
+             * @return array<int, UserInputWithAttribute>
+             */
             public function listUsers(
                 #[Input(item: UserInputWithAttribute::class)]
                 array $users,
             ): array {
                 return $users;
             }
         };

92-110: Good edge case coverage.

Tests the important scenario of empty arrays. Consider adding PHPDoc annotations for consistency with other fixes:

         $controller = new class {
+            /**
+             * @param array<int, UserInputWithAttribute> $users
+             * @return array<int, UserInputWithAttribute>
+             */
             public function listUsers(
                 #[Input(item: UserInputWithAttribute::class)]
                 array $users,
             ): array {
                 return $users;
             }
         };

216-238: Consider strengthening the assertion.

The test correctly validates the scenario, but the assertion could be more comprehensive:

         // Should create a regular ArrayObject with the nested query data
         $this->assertInstanceOf(ArrayObject::class, $args[0]);
+        $this->assertCount(1, $args[0]);
+        $this->assertIsArray($args[0][0]);
+        $this->assertSame(['id' => '1', 'name' => 'test'], $args[0][0]);

This would ensure the ArrayObject contains the raw nested data as expected.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3c476e and 10756d1.

📒 Files selected for processing (2)
  • src/InputQuery.php (4 hunks)
  • tests/ArrayInputTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/InputQuery.php
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/ArrayInputTest.php (4)
src/InputQuery.php (1)
  • InputQuery (46-375)
tests/Fake/CustomArrayObject.php (2)
  • CustomArrayObject (9-15)
  • getFirst (11-14)
tests/Fake/UserInputWithAttribute.php (1)
  • UserInputWithAttribute (9-18)
src/InputQueryInterface.php (1)
  • getArguments (19-19)
🪛 GitHub Check: Coding Standards
tests/ArrayInputTest.php

[failure] 162-162:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 162-162:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 137-137:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 137-137:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 117-117:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 117-117:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 97-97:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 97-97:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.


[failure] 36-36:
Method class@anonymous::listUsers() does not have @return annotation for its traversable return value.


[failure] 36-36:
Method class@anonymous::listUsers() does not have @param annotation for its traversable parameter $users.

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Psalm
  • GitHub Check: Psalm
🔇 Additional comments (6)
tests/ArrayInputTest.php (6)

16-24: LGTM!

Clean test class setup following PHPUnit best practices.


59-90: LGTM!

Comprehensive test for ArrayObject parameter handling with proper validation of the resulting collection.


112-130: LGTM!

Proper handling of missing array parameters defaulting to empty array.


132-150: LGTM!

Good defensive test for handling non-array values gracefully.


152-176: Excellent error handling test.

Properly validates that non-array elements in the input array throw a descriptive exception.


178-214: Comprehensive test for ArrayObject inheritance.

Excellent coverage of custom ArrayObject subclass functionality, including validation of custom methods.

@koriym koriym merged commit a788bf0 into 1.x Jul 3, 2025
18 checks passed
koriym added a commit that referenced this pull request Jul 6, 2025
Add array and ArrayObject support for Input attribute
@coderabbitai coderabbitai bot mentioned this pull request Jul 16, 2025
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