Skip to content

Instantly share code, notes, and snippets.

@IMSoP
Created August 28, 2024 21:29
Show Gist options
  • Save IMSoP/16e2422d86e3ab513d6b0658009d0c06 to your computer and use it in GitHub Desktop.
Save IMSoP/16e2422d86e3ab513d6b0658009d0c06 to your computer and use it in GitHub Desktop.
Default parameter type variance examples for https://wiki.php.net/rfc/default_expression
<?php
namespace Acme\Log {
interface Logger {
public function log(string $message, int $level=5);
}
function use_acme_logger(Logger $logger) {
$logger->log('Hello', default - 1);
}
}
namespace OmniCorp\Log {
interface Logger {
public function log(string $message, string $level='ERROR');
}
function use_omnicorp_logger(Logger $logger) {
$logger->log('Hello', default . '!');
}
}
namespace MyCoolApp {
class MyCoolLogger implements \Acme\Log\Logger, \OmniCorp\Log\Logger {
// Since we can widen the input type, we can supply the same class to libraries using both interfaces
// It doesn't actually matter which type we pick for the default, but we can't keep both
// So either use_acme_logger or use_omnicorp_logger is going to break, even though we met the interface
public function log(string $message, int|string $level='ERROR') {
if ( is_int($level) ) {
$level = match($level) {
5 => 'ERROR',
4 => 'WARNING',
// ...
};
}
echo "$level: $message\n";
}
}
}
<?php
// Version 1.0
class Foo {
public function __construct(int $timeout=60) {
// ...
}
}
// Version 1.1
class Foo {
public function __construct(?int $timeout=null) {
if ( $timeout !== null ) {
trigger_error(
'Setting a global timeout is deprecated, use the configureTimeouts() method instead.',
E_USER_DEPRECATED
);
}
// ...
}
}
// Looks reasonable in version 1.0, passes a default of 0 after upgrading to version 1.1
$foo = new Foo(default * 2);
<?php
interface FooInterface {
public const FLAG_PRETTY = 0b0001;
public const FLAG_FAST = 0b0010;
public function bar(int $flags = self::FLAG_PRETTY);
}
function use_a_foo(FooInterface $foo) {
// Seems safe, interface says it's an optional int, so I can do bitwise ops on the default
$foo->bar(default | ForInterface::FLAG_FAST);
}
use_a_foo(new BetterFoo); // TypeError! Cannot use bitwise or on object of type FooOptions
class FooOptions {
public bool $pretty=true;
public const SPEED_SLOW=1;
public const SPEED_FAST=2;
public const SPEED_CRAZY=3;
public int $speed = self::SPEED_SLOW;
public static function fromBitFlags(int $flags): self {
// ...
}
}
class BetterFoo implements FooInterface {
public function bar(int|FooOptions $flags = new FooOptions) {
if ( is_int($flags) ) {
$flags = FooOptions::fromBitFlags($flags);
}
// carry on using FooOptions
}
}
<?php
// Version 1.0
class ApiClient {
public function __construct(private HttpClient $httpClient = new HttpClient) {}
}
// User code
$client = new ApiClient( default->withUserAgent('AwesomeApp/666') );
// Version 1.1
class ApiClient {
public function __construct(private NetworkClient $httpClient = new WebSocketClient) {}
}
// User code now breaks because WebSocketClient doesn't have a withUserAgent() method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment