Why doesn't PHP's null coalescing operator (??) work on class constants with different visibilities?

自作多情 提交于 2019-12-12 12:22:35

问题


Consider the example below. Class a has private const SOMETHING, but class b has 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();

Output:

This is b!

But now if I comment out the definition for SOMETHING in class b, an error is thrown:

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();

Output:

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

However, changing the visibility from private const SOMETHING to protected const SOMETHING in class a fixes this.

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();

Now the output is as expected:

This is a!

I don't understand why php is evaluating b::SOMETHING prior to applying the null coalescing operator, which according to the documentation:

The null coalescing operator (??) has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with isset(). It returns its first operand if it exists and is not NULL; otherwise it returns its second operand.

Since b::SOMETHING is not set, why doesn't the first example work and a consistent visibility is required for the constant in the base class?


回答1:


Thanks to @Devon and @Dormilich for their responses.

TL;DR: You can't use the null coalescing operator (??) with constants. You have to use defined() instead.

According to the documentation for the null coalescing operator (??):

The null coalescing operator (??) has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with isset(). It returns its first operand if it exists and is not NULL; otherwise it returns its second operand.

Meaning that $x ?? $y is shorthand for isset($x) ? $x : $y. And this is where the problem lies, because the documentation for isset explicitly states:

Warning: isset() only works with variables as passing anything else will result in a parse error. For checking if constants are set use the defined() function.

That's what throws the fatal php error I describe in the question. Instead, a solution would be to do away with the null coalescing operator and replace it with 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();

A second solution is to change how the code works in the first place. As @Devon correctly points out, the private visibility of a::SOMETHING prevents class b from seeing it, so b::SOMETHING is not defined. However, when the visibility of a::SOMETHING is changed to protected, class b can see it and b::SOMETHING references it. This code doesn't need the null coalescing operator at all, and can just use static::SOMETHING without any conditional:

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();



回答2:


Since b::SOMETHING is not set, why doesn't the first example work and a consistent visibility is required for the constant in the base class?

B::SOMETHING is set. It's set because B extends A and you've defined SOMETHING as a constant of A. The problem isn't that it is not set, the problem is that you haven't granted B access to it, so it really doesn't fit into the null coalescing format.

This really comes down to the improper use of private visibility.




回答3:


As you admitted before :

The null coalescing operator (??) has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with isset(). It returns its first operand if it exists and is not NULL; otherwise it returns its second operand.


This mean that this operator works correct in your second code block because in class b const SOMETHING exist! but it unaccessible. You can't check existent nor 'NULL value state' of existed encapsulated fields with ?? or with isset().

I guess the check logic of encapsulated field works in manner of :
Exists? ->yes-> is Null? -> Error(Can't access it to check value)

Other code block block works as they should

来源:https://stackoverflow.com/questions/50971530/why-doesnt-phps-null-coalescing-operator-work-on-class-constants-with-dif

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!