On 26 August 2024 22:27:55 BST, Matthew Weier O'Phinney 
<mweierophin...@gmail.com> wrote:
>How exactly is this worrisome? Consider this:
>
>    class A {
>        public function __construct(private LogInterface $logger = new
>DefaultLogger()) { }
>    }
>
>    class ProxiedLogger implements LogInterface { ... }
>
>    $a = new A(new ProxyLogger(default));
>
>If class A is programming to the `LogInterface` contract, the fact that it
>gets a proxied version of the default should not matter in the least. Being
>able to proxy like this means that a _consumer_ of class A does not need to
>know or care what the default implementation is; they can assume it follows
>the same contract, and proxy to it regardless. The upstream developer
>doesn't need to care, because they are programming to the interface, not
>the implementation. This doesn't violate the separation of concerns
>principle, nor covariance.


On a technicality, it doesn't violate any variance rules, because PHP doesn't 
treat constructors as substitutable; although rules of API stability between 
versions generally do.

For any other method, it would violate variance principles because they 
guarantee that the method accepts *at least* LogInterface, not *exactly* 
LogInterface. A child class can change the parameter type to any superset - a 
parent interface, a nullable type, a union, or mixed - and choose a default to 
match.

As Bruce Weirdan astutely pointed out 
[https://externals.io/message/125183#125274] the caller is treating the default 
value as though it's an *output*, which would need to be *covariant* (allows 
narrowing), but it's actually an *input* so is *contravariant* (allows 
widening).

If we state that default values, or their types, can reliably be inspected and 
used by callers in this way, we would logically need to declare any optional 
parameter *invariant* (must match exactly in all child classes), which would be 
a huge loss of functionality.

Regards,
Rowan Tommins
[IMSoP]

Reply via email to