为啥 PHP 的空合并运算符 (??) 不能处理具有不同可见性的类常量?

Posted

技术标签:

【中文标题】为啥 PHP 的空合并运算符 (??) 不能处理具有不同可见性的类常量?【英文标题】:Why doesn't PHP's null coalescing operator (??) work on class constants with different visibilities?为什么 PHP 的空合并运算符 (??) 不能处理具有不同可见性的类常量? 【发布时间】:2018-11-30 23:06:51 【问题描述】:

考虑下面的例子。 a 类有private const SOMETHING,但 b 类有protected const SOMETHING

class a 
    private const SOMETHING = 'This is a!';

    public static function outputSomething() 
        return static::SOMETHING ?? self::SOMETHING;
    


class b extends a 
    protected const SOMETHING = 'This is b!';


echo (new b())::outputSomething();

输出:

This is b!

但是现在如果我在 b 类中注释掉SOMETHING 的定义,就会抛出错误:

class a 
    private const SOMETHING = 'This is a!';

    public static function outputSomething() 
        return static::SOMETHING ?? self::SOMETHING;
    


class b extends a 
    //protected const SOMETHING = 'This is b!';


echo (new b())::outputSomething();

输出:

Fatal error: Uncaught Error: Cannot access private const b::SOMETHING in file.php:7

但是,将 a 类中的可见性从 private const SOMETHING 更改为 protected const SOMETHING 可以解决此问题。

class a 
    protected const SOMETHING = 'This is a!';

    public static function outputSomething() 
        return static::SOMETHING ?? self::SOMETHING;
    


class b extends a 
    //protected const SOMETHING = 'This is b!';


echo (new b())::outputSomething();

现在输出如预期:

This is a!

我不明白为什么 php 在应用空合并运算符之前评估 b::SOMETHING,根据 the documentation:

已添加空合并运算符 (??) 作为语法糖 对于需要结合使用三元的常见情况 伊塞特()。如果存在且不为 NULL,则返回其第一个操作数; 否则返回第二个操作数。

由于未设置 b::SOMETHING,为什么第一个示例不起作用并且基类中的常量需要一致的可见性?

【问题讨论】:

self和static的区别在***.com/questions/5197300/new-self-vs-new-static中有描述 另外一个问题是isset()不能检查static::SOMETHING(致命错误:不能在表达式的结果上使用isset()) 如果您希望能够在子类中定义,为什么要将 SOMETHING 设置为私有或常量?似乎是一个糟糕的定义选择。 @Dormilich,这就是我最初的想法,但是当类常量具有一致的可见性时它会起作用。文档说要使用defined 作为常量,所以我想我的问题的后续行动是,如果它不应该在任何情况下工作,为什么它可以工作? @Devon 这样做不是很好的做法,但在我的用例中有性能考虑。事实证明,以这种方式直接从常量加载数据(尤其是数组数据)比使用在子类中需要时覆盖父函数的函数的“正确”继承要快得多。 【参考方案1】:

感谢@Devon 和@Dormilich 的回复。

TL;DR:您不能将空合并运算符 (??) 与常量一起使用。您必须改用defined()

根据the documentation for the null coalescing operator (??):

已添加空合并运算符 (??) 作为语法糖 对于需要结合使用三元的常见情况 伊塞特()。如果存在且不为 NULL,则返回其第一个操作数; 否则返回第二个操作数。

意思是$x ?? $yisset($x) ? $x : $y 的简写。这就是问题所在,因为documentation for isset 明确指出:

警告:isset() 仅适用于传递其他任何内容的变量 将导致解析错误。检查是否设置了常量使用 defined() 函数。

这就是引发我在问题中描述的致命 php 错误的原因。相反,一个解决方案是取消空合并运算符并将其替换为defined()

class a 
    private const SOMETHING = 'This is a!';

    public static function outputSomething() 
        return defined('static::SOMETHING') ? static::SOMETHING : self::SOMETHING;
    


class b extends a 
    //protected const SOMETHING = 'This is b!';


echo (new b())::outputSomething();

第二种解决方案是首先更改代码的工作方式。正如@Devon 正确指出的那样,a::SOMETHINGprivate 可见性阻止了 b 类看到它,因此未定义 b::SOMETHING。但是,当a::SOMETHING 的可见性更改为protected 时,b 类可以看到它并且b::SOMETHING 引用它。这段代码根本不需要空值合并操作符,可以直接使用static::SOMETHING,不带任何条件:

class a 
    protected const SOMETHING = 'This is a!';

    public static function outputSomething() 
        return static::SOMETHING;
    


class b extends a 
    //protected const SOMETHING = 'This is b!';


echo (new b())::outputSomething();

【讨论】:

但是 PHP 开发团队为什么不简单地将这个非常有用的 ?? 运算符也扩展到常量。【参考方案2】:

正如你之前承认的那样:

已添加空合并运算符 (??) 作为语法糖,用于需要将三元组与 isset() 结合使用的常见情况。如果存在且不为 NULL,则返回其第一个操作数;否则返回第二个操作数。
这意味着该运算符在您的第二个代码块中正确工作,因为在 b 类中存在 const 一些东西!但它无法访问。您不能使用 ?? 检查现有封装字段的存在或“NULL 值状态”。或使用 isset()。 我猜封装字段的检查逻辑的工作方式是:Exists? ->yes-> is Null? -> 错误(无法访问它来检查值) 其他代码块可以正常工作

【讨论】:

【参考方案3】:

由于未设置 b::SOMETHING,为什么第一个示例不起作用并且基类中的常量需要一致的可见性?

B::SOMETHING 已设置。之所以设置它是因为 B 扩展了 A 并且您已将 SOMETHING 定义为 A 的常量。问题不在于它没有设置,问题在于您没有授予B 对其的访问权限,因此它确实不适合空合并格式。

这真的归结为private可见性的不当使用。

【讨论】:

以上是关于为啥 PHP 的空合并运算符 (??) 不能处理具有不同可见性的类常量?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在 Wordpress 中创建新帖子?警告:从第 716 行 /public_html/wp-admin/includes/post.php 中的空值创建默认对象

F#中的空合并运算符?

带返回的空合并运算符 (??)

是啥 ?。运营商和我可以在哪里使用? JavaScript中的空值合并运算符[重复]

ES11中的空值合并运算符

为啥不能重载三元运算符?