Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 829
Anton Avatar asked Jun 21 '18 14:06

Anton


People also ask

What is use of NULL coalesce operator PHP?

In PHP 7, a new feature, null coalescing operator (??) has been introduced. It is used to replace the ternary operation in conjunction with isset() function. The Null coalescing operator returns its first operand if it exists and is not NULL; otherwise it returns its second operand.

What is the use of null coalescing operator in C #?

The null-coalescing operator ?? returns the value of its left-hand operand if it isn't null ; otherwise, it evaluates the right-hand operand and returns its result.

Is null ternary operator PHP?

Ternary Operator throws e-notice if left operand is null, while null coalescing operator does not throws e-notice if left operand does not exist. Ternary Operator checks whether the value is true, but Null coalescing operator checks if the value is not null.


1 Answers

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();
like image 115
Anton Avatar answered Nov 14 '22 23:11

Anton