Skip to content

[Bug]: Docblocks above dynamic rule keys are ignored in rules() method #994

@hosni

Description

@hosni

What happened?

When using a dynamic array key inside a FormRequest’s rules() method, Scramble does not include the PHPDoc description written above it.
It works perfectly with static keys but fails when the key is generated dynamically.

Scramble should attach the docblock description to the field even when the key is defined using a dynamic expression, as long as the key can be resolved at runtime or the expression is documented above it.

How to reproduce the bug

Given a Laravel FormRequest:

public function rules()
{
    return [
            /**
             * The configuration for `fcm` channel.  
             * It is required when you send `fcm` in channels.
             */
            'fcm' => [
                Rule::requiredIf(fn(): bool => $this->needConfigurationForChannel(ChannelEnum::FCM)),
            ],
            /**
             * The Firebase registeration token of the user on it's device.  
             * This is the `token` field which is exists in result of call to:  
             *  https://fcmregistrations.googleapis.com/v1/projects/{PROJECT_ID}/registrations
             */
            'fcm.token' => [
                Rule::requiredIf(fn(): bool => $this->needConfigurationForChannel(ChannelEnum::FCM)),
                'string',
                'min:100',
                'max:200',
            ],
    ];
}

✅ Works perfectly — Scramble shows the above description:

Image

But this version loses the description:

public function rules()
{
    return [
            /**
             * The configuration for `fcm` channel.  
             * It is required when you send `fcm` in channels.
             */
            $this->channelName(ChannelEnum::FCM) => [
                Rule::requiredIf(fn(): bool => $this->needConfigurationForChannel(ChannelEnum::FCM)),
            ],
            /**
             * The Firebase registeration token of the user on it's device.  
             * This is the `token` field which is exists in result of call to:  
             *  https://fcmregistrations.googleapis.com/v1/projects/{PROJECT_ID}/registrations
             */
            $this->channelName(ChannelEnum::FCM, 'token') => [
                Rule::requiredIf(fn(): bool => $this->needConfigurationForChannel(ChannelEnum::FCM)),
                'string',
                'min:100',
                'max:200',
            ],
    ];
}

❌ The field appears in the docs (if Scramble can detect it), but the description is missing:

Image

Package Version

0.12.35

PHP Version

8.3.26

Laravel Version

12.31.1

Which operating systems does with happen with?

Linux

Notes

After inspecting the source, it seems this behavior is caused by how Scramble currently handles array keys and documentation extraction:

  1. In src/Infer/Handler/ArrayItemHandler.php, the key type is set only if it is a LiteralStringType, and there’s even a @todo comment indicating dynamic keys are not yet handled:

    $keyType instanceof LiteralStringType ? $keyType->value : null, // @todo handle cases when key is something dynamic

    As a result, any dynamic key (e.g., $this->channelName(ChannelEnum::FCM)) produces an ArrayItemType_ without a resolvable string key.

  2. Then, in src/Support/OperationExtensions/ParameterExtractor/TypeBasedRulesDocumentationRetriever.php, the method getDocNodes() ignores such entries because of this check:

    if (! $key = $this->getArrayItemKey($item)) {
        return []; // <- documentation for dynamic keys is discarded here
    }

Because the $key is null for dynamic expressions, the docblock is skipped entirely — even though the ArrayItemType_ may contain a valid docNode attribute.

A possible improvement would be to preserve the PHPDoc node for dynamic keys instead of discarding it, maybe by:

  • Storing it under a placeholder name (e.g. "dynamic_key_<line_number>")
  • Or evaluate the rule and get the key

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions