Essay
OOP-NOOB Series - The Publicity Stunt
Having all methods and properties be public defeats the purpose of using OOP in the first place, as most of the benefits depend on encapsulation.
This article is part of the OOP-NOOB Series.
What Are We Talking About Here?
OOP makes use of access modifiers to control the accessibility of methods and properties. This is what allows you to use the concept of encapsulation, so that you have a public interface that consumers of your code can develop against, as well as a private implementation that needs to be treated as a black box from the outside.
Having all of your methods and properties be public generally defeats the purpose of using OOP in the first place, as most of the benefits depend on the concept of encapsulation in some form or other.
How Are Access Modifiers Being Misused
There are a few different ways people misuse OOP access modifiers in PHP, some of which are more sneaky than others.
Public Offense
The most obvious way to pull the “Publicity Stunt” is to have only public access modifiers all over the place. This results in code like this:
class PublicOffenseUser {
public $username;
public $password_hash;
public function get_username() {
return $this->username;
}
public function set_username( $username ) {
$this->username = $this->validate_username( $username );
}
public function set_password( $password ) {
$this->password_hash = password_hash( $password, PASSWORD_DEFAULT );
}
public function check_password( $password ) {
return password_verify( $password, $this->password_hash );
}
public function validate_username( $username ) {
return filter_var(
trim( $username ),
FILTER_SANITIZE_ENCODED,
FILTER_FLAG_STRIP_LOW
);
}
}}
Although this does look like a valid class, it does not make sense as an object according to OOP principles.
First of all, the validate_username() method should probably not be part of the public interface that is provided to external consumers. This is an implementation detail, and it should be made private so that it can be changed at any time without needing to consider backward compatibility with consumers of that particular code.
What’s worse though, is that the properties $username and $password_hash are public as well. So, even though we have getters and setters that try their best to enforce the consistency and integrity of these properties, any external code can directly change them at will, therefore bypassing all the integrity checks we could put in place. So, how about setting $username to -1 and $password_hash to new RuntimeException( ‘Nonsense’ )? The class will allow it, so the code should need to be able to handle them, right?
This might have the class stamp on it, but don’t be deceived! In terms of how it works, it is closer to procedural code than to OOP. We could just as well have arbitrary variables $username and $password_hash in whatever scope, and provide procedural helper functions to deal with them.
Sins Of Times Past
There’s a disguised version of the above code that looks differently, but results in the exact same behaviour:
class SinsOfTimesPastUser {
var $username;
var $password_hash;
function get_username() {
return $this->username;
}
function set_username( $username ) {
$this->username = $this->validate_username( $username );
}
function set_password( $password ) {
$this->password_hash = password_hash( $password, PASSWORD_DEFAULT );
}
function check_password( $password ) {
return password_verify( $password, $this->password_hash );
}
function validate_username( $username ) {
return filter_var(
trim( $username ),
FILTER_SANITIZE_ENCODED,
FILTER_FLAG_STRIP_LOW
);
}
}
See, no public keywords in the code above!
However, this code behaves exactly as the version before. Why’s that?
This syntax is actually PHP 4 code, from back when PHP did not yet offer a full OOP model. There were no access modifiers back then, and where you denoted a method with a simple function, you did the same for properties with var.
When you use this code in PHP 5, it offers fallbacks for the old syntax, so that old PHP 4 can still work with newer PHP versions. The only way they could provide a smooth migration from PHP 4 code to PHP 5 code was to make both the var as well as the access-modifier-less function fall back to being public. So, yes, internally, this is the exact same code than we had in the first example, with the same issues.
A Leak Of Its Own
There are some code constructs that can be very deceiving in terms of how access to properties is handled. Here’s such an example:
class ALeakOfItsOwn {
public $username;
public $email;
public $phone;
private $password_hash;
public function set_password( $password ) {
$this->password_hash = password_hash( $password, PASSWORD_DEFAULT );
}
public function check_password( $password ) {
return password_verify( $password, $this->password_hash );
}
public function __call( $method, $arguments ) {
$prefix = substr( $method, 0, 4 );
if ( ! in_array( $prefix, [ 'get_', 'set_' ], true ) ) {
return null;
}
$property = substr( $method, 4 );
if ( property_exists( $this, $property ) ) {
switch( $prefix ) {
case 'get_':
return $this->$property;
case 'set_':
$this->$property = $arguments[0] ?? null;
}
}
return null;
}
}
As you can see above, we have made the properties $username, $email and $phone public for easy access (let’s assume we don’t need validation for now). The $password_hash however is private, and it comes with a setter and a “checker” method.
Then, as we are starting to get a bit too clever, we add a magic method to the class that lets it accept arbitrary method calls. The reason we do this is that we want to reduce the boilerplate code for all the getters and setters for the three public properties. So, in essence, we have replaced 6 methods with only one general-purpose one. Nice!
The problem is that, without necessarily noticing it, we completely disarmed the access modifiers of the class. So, now, even though the $password_hash is private and it has no getter, we can still just use get_password_hash() to get the property directly, as the property is then indirectly accessed from within the class. Worse yet, although we have a set_password() method that makes sure we properly hash the value first before storing it, we can just use set_password_hash() to bypass it.
So, remember that if you use magic methods like __get(), __set(), __call() or __callStatic(), it’s on you to make sure you that you adhere to all the access modifier restrictions.
If you use PHP magic methods, it’s on you to enforce access modifier restrictions!
Proper Encapsulation
Always remember to build your objects in a way that they provide proper encapsulation. Everything that is public is to be considered an “interface”, and must have all of its inputs checked for consistency and integrity. Everything that should not be part of that public interface needs to private or protected. This then allows you to have areas of code that can be assumed to be safe from external changes and only need to be checked for integrity upon entry.
Here’s an example of how our class should look:
class EncapsulatedUser {
private $username;
private $password_hash;
public function __construct( $username, $password ) {
$this->set_username( $username );
$this->set_password( $password );
}
public function get_username() {
return $this->username;
}
public function set_username( $username ) {
$this->username = $this->validate_username( $username );
}
public function set_password( $password ) {
$this->password_hash = password_hash( $password, PASSWORD_DEFAULT );
}
public function check_password( $password ) {
return password_verify( $password, $this->password_hash );
}
private function validate_username( $username ) {
return filter_var(
trim( $username ),
FILTER_SANITIZE_ENCODED,
FILTER_FLAG_STRIP_LOW
);
}
}
There’s no uncontrolled way of changing the $username or $password_hash properties from outside of the object. So, whatever checks we include in our getters and setters can be assumed to be enforced at all times. The $username property will never contain an invalid username, and the $password_hash property will always contain the hash of a password. We directly control the integrity of our object’s internal state, thus reducing the number of potential bugs we can introduce into our system. This is one of the fundamental benefits that OOP offers over procedural code, so make sure you use it to your advantage.
Encapsulation is one of the major benefits of OOP over procedural code, use it!
Conclusion
Encapsulation is an important part of OOP. To make full use of it, use both access modifiers as well as other code constructs to control how and when the internal state of your objects can be changed.