Skip to content

Reflection does not detect optional parameter when they are in an invalid order and PHP does not see this as syntax error. #18127

Closed
@DirkGerigk

Description

@DirkGerigk

Description

I report this as bug, but it can also be an feature request.

What is the problem:

A class that defines a optional parameter before an non-optional parameter

  1. That in the first place is not an issue when running the code, if we not using the class or provide both parameter.
  2. It becomes an issue when using named parameter. If only the second parameter 'test' is set, an Exception is thrown.

Side Note:
My IDE PHP Storm normally shows an warning: Optional parameter is provided before required
But this only shows up, when both parameter don't have public
That was the reason why i noticed this hole issue.

Reflection does not detect the optional parameter when there are in the wrong order

As you can see in the provided code, the Reflection detects the parameter 'bar' in Foo as not optional.

  1. This is right, when taken the order of the parameter into account.
  2. This is wrong, when we just look at the single parameter definition.

Conclusion:

Maybe php should throw an syntax error when an wrong parameter order is detected.
Because this is invalid code, but is only detected at run-time under some special conditions.
That Reflection currently shows the result that is does, is maybe fully correct, but is also not so clear.

We are able to do new Foo(...['test'=>'bar']) and that is why i thing php must throw an error when wrong parameter order is detected in the first place.

The following code:

<?php declare(strict_types=1);
class Foo {
    public function __construct(
        public string $bar = 'hallo',
        public string $test,
    )
    {}
}
class Bar {
    public function __construct(
        public string $test,
        public string $bar = 'hallo',
    )
    {}
}
foreach([Foo::class,Bar::class] as $cls){
    foreach(new ReflectionClass($cls)->getConstructor()->getParameters() as $parameter){
        print $cls.' '.$parameter->name.' '.($parameter->isOptional()?'optional':'').PHP_EOL;
    }
}

//no issue
new Foo('Hello','World');

//var_export((array)new Bar(test:'Hello'));
//array ('test' => 'Hello','bar' => 'hallo')

//var_export((array)new Foo(test:'world'));
//PHP Fatal error:  Uncaught ArgumentCountError: Foo::__construct(): Argument #1 ($bar) not passed

Resulted in this output:

Foo bar 
Foo test 
Bar test 
Bar bar optional

But I expected this output instead:

//Some kind of syntax error thrown when optional parameter is before an non-optional parameter

PHP Version

PHP 8.4.5

Operating System

No response

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