Kinds of objects
For purposes of our discussion, let's separate our objects into three different kinds:
These are the objects that get work done. They move money from one checking account to another, fulfill orders, and all of the other actions that we expect business software to take.
Domain logic objects normally do not require accessors (getters and setters). Rather, you create the object by handing it dependencies through a constructor, and then manipulate the object through methods (tell, don't ask).
Data Transfer Objects are pure state; they don't contain any business logic. They will always have accessors. They may or may not have setters, depending on whether or not you're writing them in an immutable fashion. You will either set your fields in the constructor and their values will not change for the lifetime of the object, or your accessors will be read/write. In practice, these objects are typically mutable, so that a user can edit them.
View Model objects contain a displayable/editable data representation. They may contain business logic, usually confined to data validation. An example of a View Model object might be an InvoiceViewModel, containing a Customer object, an Invoice Header object, and Invoice Line Items. View Model objects always contain accessors.
So the only kind of object that will be "pure" in the sense that it doesn't contain field accessors will be the Domain Logic object. Serializing such an object saves its current "computational state," so that it can be retrieved later to complete processing. View Models and DTO's can be freely serialized, but in practice their data is normally saved to a database.
Serialization, dependencies and coupling
While it is true that serialization creates dependencies, in the sense that you have to deserialize to a compatible object, it does not necessarily follow that you have to change your serialization configuration. Good serialization mechanisms are general purpose; they don't care if you change the name of a property or member, so long as it can still map values to members. In practice, this only means that you must re-serialize the object instance to make the serialization representation (xml, json, whatever) compatible with your new object; no configuration changes to the serializer should be necessary.
It is true that objects should not be concerned with how they are serialized. You've already described one way such concerns can be decoupled from the domain classes: reflection. But the serializer should be concerned about how it serializes and deserializes objects; that, after all, is its function. The way you keep your objects decoupled from your serialization process is to make serialization a general-purpose function, able to work across all object types.
One of the things people get confused about is that decoupling has to occur in both directions. It does not; it only has to work in one direction. In practice, you can never decouple completely; there is always some coupling. The goal of loose coupling is to make code maintenance easier, not to remove all dependencies.
Best Answer
I've had this kind of philosophical debate with myself before. Here's where I stand most of the time although I realize this is an opinion-based answer:
One thing I see that might help answer the question is the passing of $params which may or may not have attributes/array members that are set.
Over the years I've come to this conclusion:
Avoid the passing of arrays.
Why? Well, there is no way to set or have sentinel values defined for optional passed arguments.
In other words, with the code you specified, you can't do something like this:
$arg1 and $arg2 are optional arguments - if not passed they have NULL and DEFAULT_VAL respectively - no need to check explicitly.
Maybe this seems kind of arbitrary.
I think I get what you're trying to accomplish - the passing of a single reference as opposed to tons of arguments. This brings me to my next consclusion:
If not passing "atomic" variables (strings, integers, literals) then pass objects.
There are performance benefits here since passing objects is done by reference (although arrays I think are the roughly the same inside PHP).
So you might do something like:
The passed object argument would then be guaranteed to have "property1", "property2" albeit possibly with default values themselves.
Additionally, here you can type hint and a good IDE will correctly autosuggest code completion as well.
But we quickly realize we have a chicken and an egg thing going on: you're constructing an object with passed object arguments that themselves need constructing at some point.
So where does this leave us? Well, I've come to the conclusion that eventually all classes distill down to, for lack of a better term, "atomic" variables (strings, floats, doubles, ints, resources you get my point), and that I tend to try to construct all classes with those variable types or objects - but not arrays.
So did I answer your question? probably not exactly. But I hope I illustrated something useful albeit somewhat stylistic. I think the code is a bit cleaner, more readable, and less costly.
Now, this isn't to say you shouldn't sanitize your input. That's another discussion entirely.
Hope this helps.