PHP 8.3: 细粒化 DateTime 异常
在 PHP 8.3 中,Date/Time
扩展引入了特定于扩展的颗粒度异常和错误类,以便更好地表达错误和异常的状态。这使得捕获日期相关的异常更为简单和干净。
在 PHP 8.3 之前,DateTime
扩展使用标准的 \Exception
和 \Error
。
新增的 Exception/Error 类继承了现有的 \Error
和 \Exception
类,这意味着现有捕获 \Exception
或者 \Error
异常的代码,应该可以继续捕获这些错误。
请注意,Date/Time
扩展继续抛出 \ValueError
(当传入无效值到函数/方法中时)、\TypeError
(标准类型错误) 及 \Error
异常(尝试修改只读属性,反系列化出错等)。
新增异常类
PHP 8.3 添加了九个 Exception/Error 类到 Date/Time
扩展中。用户空间 PHP 代码也可以抛出这些异常,虽然按照惯例,这些异常只未 DateTime
扩展保留。
Date/Time
异常类的结构
以下图表显示了新的 DateError
Throwable
├── Error
| └── DateError
| ├── DateObjectError
│ └── DateRangeError
└── Exception
└── DateException
├── DateInvalidTimeZoneException
├── DateInvalidOperationException
├── DateMalformedStringException
├── DateMalformedIntervalStringException
└── DateMalformedPeriodStringException
DateError
及它的子类 (DateObjectError
和 DateRangeError
)为 Date 扩展自身或者 PHP 运行时的错误保留的,与传递给 Date/Time
类或函数的实际值无关。
DateException
及其子类是在值本身无效或者畸形时抛出。对于接受用户提供或者动态日期时间值的 PHP 应用,DateException
是适合于捕捉的异常。
DateError
如果底层的 timelib 数据库崩溃,就会抛出 DateError
错误。这不是常见的例子,为的是说明与 PHP 安装本身相关的错误。
DateObjectError
当 Date/Time 类对象没有正确初始化时,会抛出 DateObjectError
错误。一个常见的例子是,当用户空间 PHP 类扩展 Date/Time
类,但没有在构造器中调用 parent::__construct()
初始化它。
class Foo extends DateInterval {
public function __construct() {
// Does not call parent::__construct();
}
}
$interval = new Foo();
$interval->format('s');
DateObjectError: Object of type Foo (inheriting DateInterval) has not been correctly initialized by calling parent::__construct() in its constructor
此外,尝试比较未初始化的 Date/Time
对象也将导致 DateObjectError
错误:
DateObjectError: Trying to compare uninitialized DateTimeZone objects
DateRangeError
DateRangeError
错误将在尝试处理一个超过 PHP 整型值的日期时抛出。
DateRangeError: Epoch doesn't fit in a PHP integer
DateException
DateException
异常是 \Exception
子类的通用类型,会在用户提供无效值时抛出。
DateInvalidTimeZoneException
DateInvalidTimeZoneException
异常会在尝试使用不能识别的时区名实例化 DateTimeZone
类对象,或者尝试设置超出时区范围的阈值是抛出。
new DateTimeZone("DoesNotExists");
new DateTimeZone("-9999");
DateInvalidTimeZoneException: DateTimeZone::__construct(): Unknown or bad timezone (DoesNotExists)
DateInvalidTimeZoneException: DateTimeZone::__construct(): Timezone offset is out of range (-9999)
DateInvalidOperationException
DateInvalidOperationException
异常会在尝试对 DateTime 对象进行无效操作时抛出。当前,在特殊的时间规范上调用 DateTimeInterface::sub
会抛出该异常。PHP 8.3 之前,这将导致 PHP 警告。
$now = new DateTimeImmutable("1992-09-16 10:44:00 CET");
$e = DateInterval::createFromDateString('next wednesday');
$now->sub($e);
DateInvalidOperationException: DateTimeImmutable::sub(): Only non-special relative time specifications are supported for subtraction
DateMalformedStringException
DateMalformedStringException
异常会在 DateTime 扩展不能从给定的字符串中解析处有效的日期/时间时抛出。最常见的情况是,DateTime
/DateTimeImmutable
对象使用无效的日期/时间字符串实例化时。
new DateTimeImmutable('half-life 3 release date');
DateMalformedStringException: Failed to parse time string (half-life 3 release date) at position 0 (h): The timezone could not be found in the database
DateMalformedIntervalStringException
类似于 DateMalformedStringException
,DateMalformedIntervalStringException
异常在 Date/Time 扩展类碰到无效时间间隔字符串时抛出。
new DateInterval('until tomorrow');
new DateInterval('1992-09-16T10:44:00Z');
DateInterval::createFromDateString('next wednesday 10:44');
DateMalformedIntervalStringException: Unknown or bad format (until tomorrow)
DateMalformedIntervalStringException: Failed to parse interval (1992-09-16T10:44:00Z)
Uncaught DateMalformedIntervalStringException: String 'next wednesday 10:44' contains non-relative elements
DateMalformedPeriodStringException
DateMalformedPeriodStringException
异常在尝试使用格式错误的字符串初始化 DatePeriod
类实例时抛出。
new DatePeriod('1 mississippi');
new DatePeriod('10D');
new DatePeriod("R4");
new DatePeriod("2012-07-01T00:00:00Z/P7D");
DateMalformedPeriodStringException: Unknown or bad format (1 mississippi)
DateMalformedPeriodStringException: Unknown or bad format (10D)
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain a start date, "R4" given
DateMalformedPeriodStringException: DatePeriod::__construct(): Recurrence count must be greater than 0
Exception 类 Polyfill
虽然不可能将新的颗粒度异常更改移植到老版本中,但可以将这些异常/错误类 polyfill 补丁到老版本中。如果在 PHP >8.3 环境中,应用需要在抛出异常之前反系列化异常对象,这可能会有所帮助。PHP 应用或库决定使用新的 Exception/Error 类时,这也很有用。
class DateError extends Error {}
class DateObjectError extends DateError {}
class DateRangeError extends DateError {}
class DateException extends Exception {}
class DateInvalidTimeZoneException extends DateException {}
class DateInvalidOperationException extends DateException {}
class DateMalformedStringException extends DateException {}
class DateMalformedIntervalStringException extends DateException {}
class DateMalformedPeriodStringException extends DateException {}
向后兼容性影响
从技术上讲,这是向后兼容性破坏,因为在 PHP < 8.2 中的特定警告会在 PHP 8.3 及以后的版本中变成异常。以下异常会替换 PHP 8.2 及此前的版本中的警告。
DateRangeError
:Epoch doesn't fit in a PHP integer
DateInvalidOperationException
:Only non-special relative time specifications are supported for subtraction
DateMalformedIntervalStringException
:String '%s' contains non-relative elements