I know the textbook answer is so that every single possible property can become some mutable chain of events so you can swap names or side-effects without the caller knowing, but I've yet to find a use for that in real life.
It just leads to builder patterns and other oddities like forced decorators like Java has everywhere. I felt like beans were the realization that perhaps we shouldn't be doing this.
They are very useful when modeling CRUD-dy objects, because you can lazy-load infrequently-accessed child object using getters.
It makes for a cleaner OOP-y interface in which the caller only cares about actually exposed properties and not methods to get and manipulate hidden properties.
IMHO using properties directly is a much more natural way to talk about objects, than having a bunch of methods to get properties. Getters and setters also help ensure that methods are only for changing an object's state, not getting an object's state. For example:
class User{
public ForumsPost $LastForumsPost{
get{
if(!isset($this->LastForumsPost)){
$this->LastForumsPost = <get from database...>;
}
// Return the cached property so we don't keep hitting the database.
return $this->LastForumsPost;
}
}
}
print($user->LastForumsPost->Title);
print($user->LastForumsPost->PostId);
// instead of...
print(($user->GetLastForumsPost())->Title);
print(($user->GetLastForumsPost())->PostId);
I appreciate the detailed answer, thanks. However, It feels like $a->foo vs $a->foo() is a personal preference that hides the fact you're doing work behind the scenes.
Then again, I'm not a fan of code that does magical things that aren't apparent. Makes it a lot harder to 1) reason about and 2) solve performance issues. I also don't want the overhead of function lookups for every property access.
Getters aren't for everything. But for a basic CRUD case like above, they're a nice way of having a clean OOP contract by demarcating a clear difference between properties, which are pieces data attached to the object that the caller can read and write, and methods, which are the caller can change the object's or app's state, or perform actions with possible side-effects.
Very often for a typical CRUD app, I as the caller don't really care how we got the `LastForumsPost`. It's just an object mapping that comes from some data source like the database. And if I do care, I could get it outside of the object, and set it myself.
It's a matter of OOP modeling. Object methods are better reserved for performing actions with side effects, or complex logic or calculations, and not for getting state or simply setting public properties; and as a caller, I don't really care about the implementation details of (in my above example) getting the last forums post, I just care that it's a property I can access. (Maybe it came from a cache and not the database? Maybe it was set earlier in the script? I don't care.)
Putting it behind a getter doesn't "hide" control flow. It just makes for a cleaner interface from the caller's perspective.
FWIW, I almost never use setters. Getters are are much more useful, especially for lazy-loading basic properties.
Your example seems like an argument against getters. In the case of fetching posts like this I always want to know whether some logic is running. What if the operation is really heavy? When it's a getter you would think it's already loaded.
I've always been a fan because if a property turns from a simple value to a more complex interaction that might involve DB operations or other side effects then if a getter is in place you can modify the logic without needing to update widespread code.
If you are replacing a performant property with a slow network call, you are being negligent if you aren't reviewing all the callers to make sure that is okay.
In a typical ORM when you do say $Model->DateTime = 1732046457; the __set is actually checking the value and seeing oh it's an integer. Treat this as a unix timestamp. But when you run save() the query is actually converting that to a Y-m-d H:i:s (for MySQL/MariaDB). This doesn't actually happen until you run save() when it makes the query and runs the network call. Most of that time it's actually storing everything in memory.
But you might want to support string date times and the PHP object DateTime as well. So a typical well designed ORM converts multiple PHP types into a SQL string for saving. That's what the historical __set and __get are all about. This is called "mapping" by most ORMs and a very well designed modern one like Doctrine will actually have mappings for different SQL storage engines available.
Some light weight ORMs do not require you to define all the properties ahead of time. They pull the field names from the table and generate the model record on the fly for you. This generally lets you prototype really fast. Laravel's Eloquent is known to do this. It's also useful for derived SQL fields when you use custom queries or join from other tables. Also kinda fun to do it for properties backed by a method. As in $record->isDirty will run isDirty() under the hood for you. All these things can be documented with PHPDoc and static analyzers handle it just fine.
A method is supposed to be an action and a property is supposed to be data. So I don't see the desire to disguise setting data as a "setting method" rather than using the syntax of assignment.
> A method is supposed to be an action and a property is supposed to be data.
I agree! That's why it's wild to allow a setter to do literally anything.
You aren't just setting a property, there is a conversion happening under the hood.
And the reason I hate it is that code that appears to be infallible, isn't. It can have arbitrary side effects, raise exceptions, have unbounded runtime, etc.
There are many ways to make code misleading. You can write a method called plus and have it do multiplication. You can write a method that sounds safe but looks dangerous. Every language relies on programmers exercising judgement when writing the program.
In a lot of contexts you have something that requires a bit of code but really does behave like a property access, where it's more misleading to make it look like a method call than to make it look like a data access. E.g. using an ORM hooked up to SQLite embedded in the program. Or accessing properties of objects that you're using an EAV system or array-of-structs layout to store efficiently in memory. Or a wrapped datastructure from a C library that you're binding.
Of course if you make something look like a property that doesn't behave like a property then that's confusing. Programmers have to exercise judgement and only make things look like properties when they behave like properties. But that's not really any different from needing to name methods/objects/operators in ways that reflect what they do.
It's abstraction. Your not supposed to care that the setter is doing anything. The class is providing you an interface -- what it does with that interface is not your concern. I hate to quote Alan Kay but all objects should just respond to messages. Whether that message corresponds to a method or a property is pure semantics.
I sometimes use getters and setters to provide backwards compatibility. What was just maybe a simple data field a decade ago doesn't even exist anymore because some major system has changed and we aren't rewriting every single application when we can provide the values that they need easily enough.
If you know that setters exist then you already know that the code can do more. It's not a huge mental leap from property setting to method calls. You should never assume anything is infallible. I don't think classes should even expose raw fields.
Getters are great for manipulating data on the way out. For instance, inside the class you might store a raw representation of the data like a json string, but when reading an attribute you will decode that json string into a datastructure and pluck a certain item. I use dynamic getters often to wrap a raw value with some kind of fallback: do this if no data is present, do this if the data is present but is ancient (ie a seamless upgrade of a v1 internal datastructure to a v2 output), etc. For setters, I try not to have implicit side effects but in certain cases where it makes sense, it is nice to be able to centralize that logic: "if this value changes, ensure x and y occur" which you get "for free throughout your codebase when you use a setter, versus having to go thru the entire codebase and sprinkle the do_x() and do_y() calls (which then becomes additional footprint to maintain and worry about)
Getters/setters are just as easily associated with hidden behavior that makes systems worse.
The only sibling comment of yours that bothered to even show code just demonstrated how they turned user.lastPost into a method that makes a hidden DB query.
Yet the benefits are supposedly so nuanced and hard to get across that merely asking about it gets a "you'll understand when you get some real experience ;)" comment from you.
I've been programming for 30 years, and getters/setters are both pointless and anti-productive.
They hide (potentially a lot of) code from people reading the program, which leads to a lot of "wtf" moments later.
Magic in general is bad, and it's the kind of "look how concise and clever I am" thinking that leads to unmaintainable software.
If setting a property isn't straightforward, make it private/protected and add explicit getter/setter methods. It's more work today and much less work later on.
I know the textbook answer is so that every single possible property can become some mutable chain of events so you can swap names or side-effects without the caller knowing, but I've yet to find a use for that in real life.
It just leads to builder patterns and other oddities like forced decorators like Java has everywhere. I felt like beans were the realization that perhaps we shouldn't be doing this.