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]