In PHP, we have classes with methods inside them. Would making all your methods public
be a good idea? No, because some of them should be used only by the class they're in and not anywhere else.
What about class constants?
PHP 7.1 introduced 3 types of a class constant visibility: public
, protected
, and private
. We want some constants, such as the client hostname, to be used only in the class, but others, like parameter names, to be used anywhere.
The class constant's visibility comes in handy to make your code as tight as possible and keep your design clean. These are the options we have:
class SomeConstants
{
// can be used anywhere
const PUBLIC_CONST_A = 1;
public const PUBLIC_CONST_B = 2;
// current, child or parent class can use
protected const PROTECTED_CONST = 3;
// local class only
private const PRIVATE_CONST = 4;
}
While upgrading with legacy successful projects, I only come across projects with all constants public
by default. It's either a lack of PHP 7.1 feature awareness or avoiding the pain of adding private
and protected
manually to every single constant. If it's 10 class constants to handle, it is easy. But going through 50+ constants or even 338 constants is pain.
The downside of "public by default" is the unclear architecture, which allows constants to be used anywhere in the project. New developers who come to the project and don't know the architecture are most likely to use constants all over the project.
Imagine the same area of problems as with public methods. It's like injecting a controller to get access to the Doctrine repository of the User
class:
$homepageController = $container->get(HomepageController::class);
$doctrine = $homepageController->get('doctrine');
$userRepository = $doctrine->getRepository(User::class);
The easy question is: how to decide if the constant is private
or protected
?
Once we have this straightforward algorithm to decide on, we only have to apply it to 338 constants in our project.
The hard question is: How can we automate this to apply it to any project with any number of constants?
My approach is straightforward: battle-testing. I go to the project, make all constants private
with PHPStorm Find & Replace, and see what goes wrong. In the production? No, I take the safe path instead. I run PHPStan and see reported errors like:
Access to private constant SOME_CONSTANT of class App\SomeClass.
↓
Then we know we must turn visibility to public
:
namespace App;
class SomeClass
{
- private const SOME_CONSTANT;
+ public const SOME_CONSTANT;
}
We also look for these errors:
Access to undefined constant App\SomeClass::ANOTER_CONSTANT
This doesn't mean constant does not exist. The constant is defined, but with wrong visibility. Let's fix it to protected
:
↓
namespace App;
class SomeClass
{
- private const ANOTER_CONSTANT;
+ protected const ANOTER_CONSTANT;
}
That's it!
Well, there is one more case missed by PHPStan. That's using a particular keyword that allows parent classes to get their child values:
abstract class AbstractRepository
{
private const TABLE_NAME = null;
public function getRepository()
{
return $this->db->table(static::TABLE_NAME);
}
}
final class TripRepository extends AbstractRepository
{
private const TABLE_NAME = 'trips';
}
Why is this missed? Because the AbstractRepository
calls its default constant value, so it's valid - just wrong. We also have to check all the static::<CONSTANT_NAME>
overloaded by child classes.
Now that we have the formula for success, we look for a way to apply it at scale:
private
by defaultpublic
and protected
where neededstatic::<CONSTANT_NAME>
separately and make these protected
How can we do this with automated tools?
str_replace()
to find & replace,str_replace()
back to public
or protected
So that's what we did.
You can find a new command in the swiss-knife tool:
vendor/bin/swiss-knife privatize-constants src tests
It performs all the steps above in 25 seconds. Tested on a project with 338 public constants, including the static
case, with 1-shot and no manual fixes needed. CI is passing and ready to merge.
Give it a try to tighten your class constants instantly.
Happy coding!
Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!