Blog Single Page
UNDERSTANDING THE CONCEPT OF VISIBILITY IN OBJECT ORIENTED PHP
UNDERSTANDING THE CONCEPT OF VISIBILITY IN OBJECT ORIENTED PHP
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
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.
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.
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.
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.
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.
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.
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.
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.
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.