Skip to content

Dynamic whereFooAndBar / whereFooOrBar not validated (multi-segment) #927

@alies-dev

Description

@alies-dev

Problem

MethodForwardingHandler::columnMatchesDynamicWhere() only validates single-segment dynamic where methods. Multi-segment forms like whereFirstNameAndLastName(\$a, \$b) or whereTitleOrSlug(\$x) are accepted even when one of the segments doesn't correspond to an actual model property.

Where it breaks

src/Handlers/Magic/MethodForwardingHandler.php:581-623. The method extracts \$methodName minus the leading where, normalizes it (strip \$/_, lowercase), and compares to each pseudo_property_get_types key. Multi-segment names like firstnameandlastname will never match any single property, so the cache stores false and the call falls through. Currently the isDynamicWhereMethod check (line 296) is broader and provides a permissive signature, so whereXxxAndYyy calls slip past unnoticed regardless of whether xxx/yyy exist on the model.

Reproduction

/** @property string \$first_name */
/** @property string \$last_name */
class User extends Model {}

User::query()->whereFirstNameAndLastName('A', 'B'); // OK — valid columns
User::query()->whereFirstNameAndNope('A', 'B');     // Should warn: 'nope' not a column

The second call should raise a column-existence error but is silently accepted.

Fix sketch

Split the suffix on (?:And|Or)(?=[A-Z]) (Larastan's regex), validate each segment independently:

private static function columnsMatchDynamicWhere(
    Codebase \$codebase,
    string \$modelClass,
    string \$methodName,
): bool {
    \$suffix = \\substr(\$methodName, 5);
    \$segments = \\preg_split('/(?:And|Or)(?=[A-Z])/', \$suffix);

    if (\$segments === false || \$segments === []) {
        return false;
    }

    foreach (\$segments as \$segment) {
        if (!self::singleColumnMatches(\$codebase, \$modelClass, \$segment)) {
            return false;
        }
    }
    return true;
}

Cache key remains model:method since result depends only on the pair.

References

  • Larastan: .alies/larastan/src/Methods/BuilderHelper.php:105 (uses preg_split('/(And|Or)(?=[A-Z])/', \$finder, -1, PREG_SPLIT_DELIM_CAPTURE))
  • Laravel: Illuminate\\Database\\Eloquent\\Builder::dynamicWhere()

Related

Argument typing for dynamic where is tracked separately (forthcoming issue).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions