Very common in PHP code bases is obsessive type check, noticeably code like declare(strict_types=1) and private function( int $id ). It's basically "Always check your types".
I think a better rule is:
Check a type when the variable is created, mutated, or received from an external source. Then trust it until something happens that could change it.
The logic is simple. A variable cannot spontaneously change its type. If $id was established as an integer, and nothing has modified $id, checking whether it is still an integer doesn't make the code safer.
There are really three boundaries where checking matters:
In WordPress, hooks are a good example of the third boundary. If your code receives a value from a filter, you don't control what another plugin might have created, that's where the check belongs.
I recently encountered a plugin that required an integer as a function argument. The problem was that the value came from a WordPress hook and wasn't properly checked there.
The strict function signature caught the problem later by throwing a TypeError.
Technically, the type enforcement worked. Architecturally, it failed.
The plugin should have validated the value when it crossed the WordPress hook boundary, where it could decide how to handle an unexpected type gracefully. Instead, invalid data was allowed deeper into the application until PHP produced the fatal error.
On a production site, especially in a plugin running alongside code from many other developers, that's not necessarily the kind of "safety" we want.
Finding an invalid type is not the same as handling an invalid type correctly.
This becomes particularly relevant with OOP and private methods.
Consider:
private function my_method( int $id ){
If my_method() is private, you control every place from which it can be called.
Suppose $id was already validated when it entered the object, and every internal operation preserves that invariant. There is no outside code capable of suddenly calling this method with a string.
In that situation, the runtime type enforcement is checking something your own architecture has already guaranteed.
Instead, you can document the internal contract:
/**
* @param int $id
*/
private function my_method( $id ){
Your IDE or static-analysis tooling can warn you when your own code violates that contract, without requiring PHP to enforce the same invariant again at runtime.
Public methods are different: they are boundaries because outside code can call them. Private methods aren't necessarily boundaries.
An is_int() check or PHP parameter type check is individually extremely cheap. Performance isn't a good argument for obsessively removing a handful of them.
But unnecessary checks accumulate, and their bigger cost is often code complexity.
A defensive check needs a failure path. That means another if, fallback, exception, try/catch, or return value. Then the caller may defensively handle that failure too.
Soon we're writing code to gracefully recover from states that our architecture should have made impossible.
That's boilerplate, additional branches, additional CPU work, and additional code that has to be understood and tested.
Imagine entering an office building where your badge is checked at the entrance.
Now imagine having to show the same badge at every internal door — even though nothing between those doors could possibly have changed your identity.
That's not necessarily better security. It's mostly redundant security.
Types can be treated the same way.
Validate where trust changes.
When data enters your code, check it. When you create it, establish its type. When you mutate it, establish the new invariant.
After that, trust your own code.