OOP-NOOB Series – The Publicity Stunt
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.
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.
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.
Very good write up, Thanks Alain!Report
Lots of things to discuss here.
I have examined many a plugin and theme now in WP over past year += .6 and have saw beauty and not so much. We are talking WP right?
What I tend find (in coding) in regard to WP is its quite painful to be in or out of WP’s way w/ OOP resulting in WooPs all too often. PHP is not memory/resource friendly .vs. many languages for example. C# Core is also not real resource happy but its 600% faster than PHP 7.
Since we are speaking a multiple instance environment this becomes important. Yes, what makes no sense to be Public should be private and what should be inherited and protected etc. But strict adherence to OOP and principles thereof in PHP can be very expensive in resources.
Take simple files. I see projects with bunches and bunches of files each having their own class, another having interface, another the inherited base class across buckets of functionality. Load time especially in a shared hosting platform with physical (not SSD) drives can be VERY expensive towards performance and we’ve not processed a line of code. One instance, no biggie, 5000 connections let say, expensive. If concurrent ouch, if not, many a shared host and/or VPS has IOops. A limitation on input/output be that to the filesystems and/or DB.
I dont adore procedural code and very much like good OOP, its sorta like art. But, that said, where in the WP ecosystem of the real world usage is it “beyond” reasonable?
Example: I coded a forms processor for options pages after looking at the WP way and CMB2. I could have coded this as I might in say a Win/Mac environment with lovely inheriting etc. Instead, I went at the class in a loosely logic bound class. I guess you might say, logically procedural in functions within the class. Long story short, less than 800 lines of code and thats with the Docblocks. It supports custom components (like JQ controls), does field validations on and on. Point being, I try keep in my head that people using my work are more often than not on a shared platform.
What about maintaining code? Reusability? While the spirit of open source is other devs can use the code I am not coding in the spirit of those folks using the code or modifying it. Thats up to them. I am coding so my work suits the end webmaster(s) and preferably code I can reuse for other WP projects.
Point all being, the tradeoff’s can be problematic in every aspect and many dev’s do not take this into account. “Load everything needed or not”, “Load just what is needed but its resource fat”, on and on and on and all this struggling with interface to WP more or less whatever other plugins or themes a webmaster might utilize.
In the old days when I coded video games we had to be very aware of performance and resources especially in C/C++ .vs. assembler. Now in Win/Mac/Linux applications not so much in large part due to the computers and optimized libs and assemblies. In the world of PHP some want treat in similar fashion to local applications when coding. That’s expensive to the actual users more often than not and the response from dev’s is “Well, need a stronger server” They indeed may but it should not be due to my code sitting in 300 files when it could be in 50. It should not be due to my code having slews of base classes to have assemblies friendly to other dev’s .vs. suiting my application thereof.
I like to write my own code .vs. using whatall, say CMB2. I like to know that updates of a given lib doesnt break my application thereof. We are not speaking brain science here. A video game as an example has more processing logic than 99.99% of WP plugins ever have or will.
Point all being, in the WP ecosystem where is that balance point .vs. whatall, someone coding a PHP application using CakePHP. We have in WP an extensible application. Yes, some dev’s want think it a framework, its not. Frameworks revolve around consistency and strict structure (aka OOP).
Where is the sweetspot in WP?Report
Hey Rick,
Thanks for the feedback!
I don’t agree that you should do special trade-offs when working with WordPress. It uses PHP as its language of preference, and you should therefore make use of all the modern PHP best practices that you can. Artificially lowering the quality of the code to make it fit with the WordPress Core is just doing yourself a disservice.
When in comes to performance, your code will probably fall into two main categories: simple projects and complex projects.
For simple projects, the performance difference between compact procedural code and OOP code is negligible, as the scope of the project is very limited, and there’s not really much to save in terms of processing time. A single remote network request takes several orders of magnitude more time than whatever loading and processing you might need.
For complex projects, the main goal is maintainability. Fast code that is buggy and does not reliably produce the expected results is useless. Fast code that can’t easily adapt to changing requirements is useless. Optimizing for file sizes and loading times is a form of premature optimization that leads to a series of issues down the road.
Also, if you write well-designed modular OOP code, you open up your code to high-level optimizations. Whereas your file size reductions are very limited in what results they can produce, making high-level optimizations in a large complex code base can eliminate entire code regions at run-time, cache or persist results from one request to the next, etc… These are also possible with procedural code, of course, but are likely to double your existing code size because of the complex conditional code paths and will most likely lead to a lot of weird bugs.
The comparison between modern web development and old-school game development does not really work. Traditional video game development is full of approximations and trade-offs, and only deals with locally isolated elements. Your main issues are (RAM, GPU, HDD) memory access speed limitations and frame rates. With web development, you want to produce reliable, accessible, maintainable results in a client-server environment (where you don’t control the client). A single broken URL in your HTML makes all of your performance optimizations moot. So, before having a complete and (as far as possible) error-free code base, optimizing for performance is premature and a good way to shoot yourself in the foot.
So, with all that being said, my personal take on this is that there’s no reason to do any special trade-offs for WordPress, and foregoing OOP for performance reasons is either irrelevant (simple codebase, no noticeable difference) or a mistake (complex codebase, huge maintainability drawbacks).
Cheers,
AlainReport
Greetings Alain,
Certainly there is less in common with ole’ school game development and web development but there are some conceptual similarities and some of this is likely to come full circle in scant years. That is to say given the next evo of the internet being server/smart client as Windows/Mac OS move into the cloud.
As I noted in my prior posting I’d wager most WP sites are running within a shared or VPS shared environment. Thus, resources depending on the host may well be quite finite and more often than not in shared hosts truly are.
Yes, caching is quite helpful in page based performance just like rendering frames ahead of blanking interrupts used to be in FP based types of games. The difference being a persons computer was not measuring output/input activity and throttling it based on a daily limit.
You are correct in that network response being a bottleneck of sorts but I think you may also be surprised by file load times especially in mechanical disks across a respectively visited web with concurrent sessions. Yes, lots of variables in the mix especially with a shared host.
I have several servers, a i3 Lenovo that lived as a MS App server I picked up for $100, A elder Dell SC 1435 rack server and a more modern Intel E3-1230-3. I even have an older Intel w/ 73 GB SCSI’s in it that needs a dedicated power company LOL.
On the SC1435 I have a “mock” web server (CENTOS) complete with the goodies. I’ve used it to work on other folks webs. Pull them down local, see where the bottlenecks are and try get things moving better. Example, last year I worked on a news site. No special plugins per se. Home page was making no less than 118 DB calls. Thats crazy! I mangled it down to 17. The site pegged IO Ops daily with as little as 600 visitors daily.
I think you may be quite surprised by how much latency occurs due to file loading especially on mechanical drives.
To me, the faster my code loads, does its thing and exits the better. I have zero control over what plugins a webmaster might install. I try strike a balance of using OOP as assemblies .vs. breaking things down to heaps of lower level objects depending on the plugin of course.
Does it hurt maintainability, yep, a bit anyways. Extensibility, yep, again, a bit depending again on task(s).
Funny I went through a discussion scant months back about PHP arrays .vs. objects. Arrays in PHP are near the only reason it became viable as a web platform as C++ shines in pointer based function as its not too far abstracted from CPU addressing modes per se. OOP in PHP is built atop arrays. Where did (does!) OOP shine? Collections of objects. Describing a user of a plugin in OOP thats a single instance of said person in said plugin might be clean .vs. vanilla Var’s but thats not where OOP shines. It shines when the application is managing 1,000 or a million such records and extensibility/re-use/maintaining the code.
In PHP using arrays of objects is a double whammy. Ram hungry in comparison to many other languages. I remember when developers freaked out on C/C++ inefficiency in code generation .vs. assembler (I know both albeit many years since I’ve used assembler). The C#/.NET environment gets a bad rap for “bloat” really as the assemblies often have a great deal more function than coders are generally aware of. .NET in whole is huge but it does more than any other framework that exists as well.
Since PHP is where WP lives that is what it is. I have several projects in WP that should fill some existing gaps in WP plugins nicely and preferably make enough dough that can tide me and then some over as the next generation of Internet becomes reality. I’ve had numerous glances at it and know many people close inside that transformation. Windows/MacOS are both going cloud and all our fancy stuff is going smart client. Whether PHP will thrive in that is certainly a question. I personally do not see a way it can.
For example, using a proprietary protocol used in some PS/4 games (the transport mechanism) a friend of mine at Bethesda Softworks coded an Apache extension to service the base protocol. C++ on his core i7 sending/receiving simple data held 2800+ sessions concurrent. PHP 7 delivered 112 and we wont even speak about PHP 5. C# was able to muster 1700. I dont know if he’s done others since.
Device unification sorta signals the end websites as we traditionally think of them. Instead everything shifts to more services based pluggable code assembles, data, glue. Thats the goal. Many reasons beyond the do anything/anywhere/anytime deal such as regulatory measure abilities so nations can limit populations in very granular fashions and then some.
I prefer either be financially set at that point in time or be able to transition into it having enough in the bank to do so. Hence, the WP projects I am working on.
Unfortunately, I am not a PHP expert nor am I a WP expert… But, I do see some rather gaping areas where WP webmasters would love some plugins that should result in wonderful revenues.
If I had the millions I’d just hire up a team, get the contracts in place and away we go.
From what I have saw, you and Tom be at the top of the list.Report
Hi. I was surfing a little bit around and i found something broken:
https://www.brightnucleus.com/shop/
Hope this helps you. Have a good summer.Report
I’m very late into the discussion but I wanted to say that I enjoyed reading this. Looking forward reading the next episodes ;-)Report
Hi.
In your last example, you have a
get_username()
method, which breaks encapsulation. An object should not expose its internals to the outside world. It should encapsulate data and do something with it, perform a computation, etc.Obviously we’re accessing username because we need to perform some action with it. This action should go to the User object. Nobody should be able to read the private variable of an object.
The key characteristic of OOP is that data is hidden and is closely accompanied with behaviour.Report