On 26 August 2024 22:27:55 BST, Matthew Weier O'Phinney <[email protected]> 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]