Class Constant Visibility
In this video we cover a new feature available in PHP 7.1 - Class Constant Visibility.
In short, this brings the concept of public
, protected
, and private
to const
.
Direct from the PHP 7.1 docs:
<?php
class ConstDemo
{
const PUBLIC_CONST_A = 1;
public const PUBLIC_CONST_B = 2;
protected const PROTECTED_CONST = 3;
private const PRIVATE_CONST = 4;
}
Note here that both const
, and public const
are equivalent. In other words, const
without a visibility modifier (public
/ protected
/ private
) defaults to public
.
Ok, so this seems fairly straightforward and useful.
But what if you don't yet quite understand the difference between public
, protected
, and private
?
Let's take a quick look.
Typically the most common place to first encounter these visibility modifiers are in your first steps to working with classes:
public function someFunction() {}
// or
protected function anotherFunction() {}
// or
private function yetAnotherFunction() {}
When I first encountered these, I have to say I had no clue what the differences were, but I knew public
always seemed to work. It also makes code that (on the surface) seems much easier to test. This is a large part in why beginners to testing so frequently change all class visibility to public... but that's for another discussion :)
Let's take an example:
class A
{
public function ourFunction()
{
return true;
}
}
Here we have a very basic class with one public function - ourFunction
.
We can interact with this class:
$a = new A;
$a->ourFunction(); // true
We can shorten this:
$a = (new A)->ourFunction(); // true
Ok, so nothing mindblowing here. We have a class, it has a method, we can call it.
However, what happens if we change our class to make ourFunction
a protected
method?
class A
{
protected function ourFunction()
{
return true;
}
}
And then we try to call it:
$a->ourFunction();
It blows up:
PHP Fatal error: Uncaught Error: Call to protected method A::ourFunction() from context '' in /path/to/your/file.php:8
A very similar error is thrown if using private
instead of protected
. Why?
Well, we cannot directly use anything other than the public API of a class. More on this shortly when we get to interface
's.
Let's continue with a further example:
class A
{
protected function aProtectedFunction()
{
return true;
}
}
class B extends A
{
public function someFunction()
{
return $this->aProtectedFunction();
}
}
$b = (new B)->someFunction(); // true
In Class A we define a protected function
. This function is only available internally to either itself (Class A), or anything that extends
it. In our example, Class B extends Class A, so we gain access to anything public
or protected
in Class A.
We can expose the protected function
of aProtectedFunction
by wrapping it in a public function someFunction
inside Class B.
Is this a great idea? It all depends on your project, of course. This is merely an example.
Let's continue the example:
class A
{
private function aPrivateFunction()
{
return true;
}
}
class B extends A
{
public function someFunction()
{
return $this->aPrivateFunction();
}
}
$b = (new B)->someFunction();
Now A
contains a single private function
.
Following the same pattern of trying to expose the private function
by wrapping it in the public function
won't work here. Private functions or properties (think: member variables) cannot be used by anything other than the class in which they are defined.
Ok, so knowing this, we could now take a look at an interface
:
interface ourInterface
{
public function someFunction();
}
An interface
explicitly defines the methods that a class that implements this interface needs to implement. Uh huh.
Think of an interface as a contract. Any class that conforms to the contract must do what the contract says - in other words, if the interface says a public function someFunction
must exist, then the class implementing this interface must have that function. It may have other functions, it may not, but it must have that function to comply with the interface.
An example better illustrates this:
interface ourInterface
{
public function someFunction();
}
class A implements ourInterface
{
public function someFunction()
{
return true;
}
protected function anotherFunction()
{
// not directly tied to the interface in any way
}
private function whatever()
{
// etc
}
}
So why is all this interesting in the context of Class Constant Visibility?
Well, firstly, to explain why you might need the three types of constant.
A public const
is going to be directly available to any other class that needs it. You don't need to a new
up a class to access its constants:
class A
{
const SOME_CONST = 'some constant';
public const A_PUBLIC_CONST = 'a public const'
}
// could be used by anyone:
echo A::A_PUBLIC_CONST; // a public const
class B
{
private function someFunction()
{
return A::SOME_CONST; // returns 'some constant'
}
}
Remember const
and public const
are both public
.
Again, contrived examples, likely you wouldn't want to tie class B to class A in such a crazy way. But you could. And if you can, people do, and that means it happens.
Knowing this, we can now use the Class Constant Visibility modifier in PHP 7.1 to stop this:
class A
{
protected const A_PROTECTED_CONST = 'a protected const'
}
// can only be used by classes extending A
echo A::A_PUBLIC_CONST; // throws 'call to protected method'
// note this class does not extend A
class B
{
public function someFunction()
{
return A::A_PROTECTED_CONST; // throws 'call to protected method'
}
}
class C extends A
{
private function someFunction()
{
return A::A_PROTECTED_CONST; // returns 'a protected const'
}
}
By marking our constants as private
or protected
, we can protect (excuse the pun) them from outside access in the same way that we always could with private
and protected
functions.
This is a really nice, and useful, addition to PHP 7.1
And to finish up, tracking back to the concept of interface
's, it's worth noting that you can define constants on an interface:
interface ourInterface
{
const A_CONST = 'yes this is valid';
public const B_CONST = 'and so is this';
public function someFunction();
}
echo ourInterface::A_CONST; // 'yes this is valid'
But as an interface always defines the publicly available methods / constants, it must therefore stand that we cannot define protected const
or private const
on an interface:
interface ourInterface
{
private const PRIV_CONST = 'is this valid?';
}
echo ourInterface::PRIV_CONST; // throws: 'PHP Fatal error: Access type for interface constant someInt::A_THING must be public'
And of course, you cannot define a protected const
on an interface, and if you did, it would throw the same error.