in Web and Tech

Visibility in OOP

One of the better written articles on visibility I’ve encountered.

Echoed from: https://torquemag.io/2016/05/understanding-concept-visibility-object-oriented-php/
by Josh Pollock

I’ve been writing a lot recently about principles of object-oriented programming (OOP) in PHP, including magic methods. OOP is great in that it promotes encapsulation of code: separated functions, classes, and methods — each with their own scopes and purposes. The rules of how we access properties, variables, and methods from different encapsulated scopes are determined by visibility.

In this article, I will explain how visibility is defined, how it works, and why it is important in OOP PHP.

Keep in mind that this article refers to how these principles work in PHP 5.4 or later. PHP 5.3 and below are missing key features for doing OOP properly. They are also dramatically slower than 5.4 and they are missing other important syntactic features of the language. In my opinion, due to their lack of security support, they should never be used or supported by anyone ever.

Encapsulation And Scope

In software design, we think of encapsulation as the principle by which code and data are bundled together in a way that restricts their access to the rest of the programming. The simplest example of encapsulation is a function. Consider this PHP code:

$post = get_post( 1 );
  
  function slug_get_post_five(){
  	$post = get_post( 5 );
  	var_dump( $post );
  }
  
  var_dump( $post );
  slug_get_post_five( 5 );

In this example, which isn’t using any object-oriented PHP, we have two different variables called post that are being printed with “var_dump();”. They will print two totally different things because the second “$post” is encapsulated inside of a function, while the first is not.

In this case, the fact that the second $post was encapsulated in the function “slug_get_post_five()” placed it in a different scope than the $post above it, which makes it a completely different variable. They look similar because I gave them the same name, but they have no relationship. In fact, they are stored as two separate entities in the server’s RAM when being executed.

In non-OOP PHP, we have no good way of declaring a controlling access outside of a function to a variable declared in the function. We can return one value from a function.

We can return one value from a function. We can also use global scope, but that is messy for a lot of reasons besides the lack of control over how variables are accessed from outside the function.

OOP, on the other hand, gives us the ability to control access to variables of a class, which we refer to as properties. This is one of the major advantages of OOP — we have three levels of “visibility” for both properties and methods of a class.

Classes VS Objects

Before I get into visibility I want to quickly disambiguate between classes and objects to make sure the difference between them is clear. Classes define a set of rules for objects they create. Objects are created by instantiating a class, which determines what the object can do.

We see this all the time in WordPress with the WP_Query class. In the global variable, “$wp_query” is an object of the WP_Query class that WordPress creates based on the current HTTP request. But we can create as many additional WP_Query objects as we need during a session.

Each WP_Query object has the same methods and properties, but the values of those properties, or the results of those methods, may be different. For example, let’s say that during a session created by requesting the URL for a category term archive, we create a new WP_Query object to list posts of the custom post type “product” and store it in a variable called $products.

In this case, “$products” and the global “$wp_query” are both objects of the WP_Query class. Both have a property called “$posts” that holds the queried posts, but which posts they hold is totally different. In fact, each post is represented by an object of the WP_Post class — same class but with totally different objects.

Using one class to create multiple objects used for similar purposes is good software design. It would be challenging if I had to write a custom database query every time I needed to get a few WordPress posts from the database.

Classes can also be extended by adding a second class, called a subclass, which adds to and modifies the rules of the parent class. This is very important for understanding why and how visibility works: A subclass can override a property or method.

The Three Visibility Levels

In OOP PHP we have three visibility levels for properties and methods of a class: public, protected, and private. Visibility is declared using a visibility keyword to declare what level of visibility a property or method has. The three levels define whether a property or method can be accessed outside of the class, and in classes that extend the class.

Public

The first level is “public.” This level has no restrictions, which means it can be called in any scope. This means that a public property of an object can be both retrieved and modified from anywhere in a program — in the class, a subclass, or from outside of the class, for example.

This level is the default behavior when visibility is not declared because of backward-compatibility concerns with PHP4, which did not have visibility.

Technically a method declaration does not need to be proceeded by a visibility keyword, which makes it public. Also, a property can be defined using the “var” keyword to make it public. But for future compatibility reasons, and so your code is explicit in its intent, you should always use a visibility keyword and not use the var keyword.

Protected

The second level is “protected.” Protected properties and methods can be accessed from inside the class they are declared, or in any class that extends them. They can’t be accessed from outside the class or subclass.

Private

While protected properties and methods are accessible anywhere in the object, the third level “private” is more restrictive.

A private property or method can’t be accessed by a subclass of the class it is defined in. If you have a class with a protected property and a private property and then extend that class in the subclass, you can access the protected property, but not the private property.

Rules Of Property Visibility

Take a look at this code, which shows three classes: The second and third classes extend the first, and the third will also create an error as it violates the rules of visibility within a class.

class force {

	/**
	 * This protected property can be accessed in a subclass
	 */
	protected $jedi;

	/**
	 * This private property can not be accessed in a subclass
	 */
	private $sith;

	public function set_force_users( $jedi, $sith ){
		//Both of these are legal beacuse we are in the same class, private vs protected is not a very meaningful distinction
		$this->jedi = $jedi;
		$this->sith = $sith;
	}

}

class jedi extends force {
	/**
	 * This is a good example.
	 */
	public function set_jedi( $jedi ){
		//totally legal because jedi is protected and therefore accessible in this subclass
		$this->jedi = $jedi;
	}
}

class sith extends force {

	/**
	 * Example of something bad, you know like the Sith, don't do this.
	 */
	public function set_sith( $sith ){
		//Not allowed, we can't set a private property of the parent class in a sub class
		$this->sith = $sith;
	}

}

Let’s walk through this. Our base class declares two properties: The first property, “$jedi,” is protected, while the second property, “$sith,” is private. The class uses one method to set both properties. In this case, the distinction between private and protected is meaningless because we couldn’t set their values from outside of the class, but inside it’s fine.

The second class extends the first class and adds a new way to set the protected property, “jedi.” Protected properties are accessible in subclasses, and is available throughout the object. The parent class and subclass are part of that object.

The third class, on the other hand, is where we violate the rules of visibility. The private property $sith can’t be accessed in a subclass. It can only be accessed in the class that it is defined in. You can make this legal by defining a private property called sith in the subclass, however, that is a bad workaround.

Keep in mind that if we were to instantiate the “jedi” class, and put it in the variable “$luke,” we could use the “set_jedi()” method to set the protected property “$jedi,” but we could not set it directly.

//allowed since the set_jedi method is public
$luke = new jedi;
$luke->set_jedi( 'Luke Skywalker' );

//not allowed since the jedi property is protected
$kylo = new jedi();
$kylo->jedi = 'Kylo Ren';

In the first two lines, the public method “set_jedi()” is used properly to set the protected property of the class. This is legal because the setting happens inside of the class.

The second two lines in this example would create a PHP error because we are trying to set a protected property from outside of the class. That is forbidden by the PHP interpreter. The same thing would happen if we tried to echo the property.

It is also important to know that in a subclass a property can be made more visible, but not less visible. These kinds of changes are allowed but are not considered good practice. A subclass can override a protected property of the parent class and make it public, but not make it private.

class ship{
	protected $model;
}

/**
 * Making model MORE visible is allowed but is confusing and convoluted.
 */
class crusier extends ship{
	public $model;


}

/**
 * Making model MORE visible not allowed.
 */
class star_destroyer extends ship {
	private $model;
}

Rules Of Method Visibility

I have focused on properties, rather than methods, so far because almost everything I have said about properties also applies to methods, with the exception of a few additional rules. The basic principles still apply:

  • A public method of a class can be called outside of the class or in a subclass.
  • A protected method can’t be called outside of a class, but can be called in a subclass.
  • A private method of a class can only be called inside of the class it is declared in.
class force_user{
	private $is_force_user;

	protected $name;

	public function __construct( $name, $is_force_user ){
		$this->name = $name;
		$this->set_is_force_user( $is_force_user );
	}

	private function set_is_force_user( $is_force_user ){
		$this->is_force_user = boolval( $is_force_user );
	}

	protected function is_force_user(){
		return $this->is_force_user;
	}


}

class jedi extends force_user{

	public function has_the_force(){
		//Legal because is_force_user() is protected and therefore accessible in subclass
		return $this->is_force_user();
	}
}


class sith extends force_user  {

	public function remove_force(){
		//not legal, method is private, not accessible in subclass
		$this->set_is_force_user( false );
		
		//also not legal beacuse property is private
		$this->is_force_user = false;
	}
}

In this example, the base class uses a private function to set a private property. Since it is exposed by a protected function, it can be accessed by subclasses. This is what happens in the “jedi” subclass, however since the property and the setter method are private, there is no way to change it in a subclass. The “sith” subclass attempts to use a private property and a private method declared in the parent class, which is not legal.

Just like with properties, a method can become more visible in a subclass. We could have solved one of the two problems in the “sith” class by overriding the private method with a protected method. To make that method useful, we would have had to make the property it sets protected as well. That would have worked, but it would call into question why we were using a subclass instead of just writing a whole new class.

Here is a rewritten “sith” class that implements these changes to prevent errors:

class sith extends force_user  {

	//now protected so we can access it
	protected $is_force_user;

	public function remove_force(){
		//not legal, method is private, not accessible in subclass
		$this->set_is_force_user( false );

		//also not legal beacuse property is private
		$this->is_force_user = false;
	}

	//changed to protected, which required copying its functionality manually
	protected function set_is_force_user( $is_force_user ){
		$this->is_force_user = $is_force_user;
	}
}

As you can see, there is really no point in extending the base class here. Now, if we change how the method set_is_force_user() works in the parent class, we will have to change it manually in the subclass because we have lost the utility of extending a class. These types of workarounds are sometimes necessary when working with other people’s code. This sort of thing is considered to have a bad “code smell” because it isn’t technically wrong, but that doesn’t mean it is a code idea.

With properties, there is no way to prevent changing visibility or to prevent them from being overridden. With methods there is — we can use the “final” keyword in a method declaration. Once we do, it is illegal to override them which precludes changing visibility.

The final keyword should be used lightly. While it can be used to prevent visibility changes that you might now want, it is not a great idea to use it solely for that reason. You should give other developers flexibility and freedom.

I was recently working on a project where I need to extend a class of a third-party library just so I could change one method. In that method, I needed to access a private property so I changed its visibility to protected in my subclass. Without being able to do that, I would have had to write a much more convoluted workaround, probably by overriding the constructor and making a copy of that private property there.

Why Visibility Matters

So far I’ve discussed the rules of visibility, but I haven’t really addressed why we care about it and should use it with intent.

There are a few reasons to use visibility in our OOP PHP code:

  • It helps us show the intent of our code
  • it reduces our need to validate properties when used internally
  • and it helps dictate how classes should be used.

For example, consider this class:

class lightsaber{

	public $name;

	protected $color;

	public function set_name( $name ){
		if( is_string( $name ) ){
			$this->name = $name;
		}
	}

}

In this case, we can set the property name to anything. This is a problem if we want to write code later that uses “$name” and assumes it is a string. We would have to make sure it was a string every time we used that property.

Conversely, the property color is protected. It has a public set function that validates if it is a string before setting. Now we can assume when calling that property internally that it is a string. Assuming the type of variable is a dangerous assumption in a dynamically typed language, but this is one way to reduce that risk.

Also, by having public functions for getting protected or private properties, we make it clear what the utility of a class was. For example, if I have a class that has a constructor that takes in a bunch of data, creates some markup and puts it in a protected variable called $html, and then has a public method to get that variable, it is pretty clear the point of the class was to make HTML markup.

A Few Last Words

I want to make two last points.

The first is that the complexity of these rules is one of the many reasons why you should be using an IDE with a PHP interpreter included. I use PHPstorm and when I was writing the example code for this article the “bad examples” caused PHPStorm to add red underlines to my illegal code and gave me a little pop-up explaining why I was wrong when I moused over the bad code. It also did not auto-complete the properties and methods that I couldn’t use.

The other thing to keep in mind is not to overuse visibility. As I said in the last section, visibility helps us clarify the intent of our code, but by being overly-pedantic you can make life more difficult for yourself, or another developer working with your code, down the road.

Now that you know how visibility works and why it works, you will be able to write better code with clearer intent. You should also be able to take better advantage of PHP’s ability to extend classes, which makes a big difference in writing more reusable code.

Write a Comment

Comment