Property Hooks

Developers often use methods to wrap and guard access to object properties. There are several highly common patterns for such logic, which in practice may be verbose to implement repeatedly. Alternatively, developers may use __get and __set to intercept reads and writes generically, but that is a sledge-hammer approach that intercepts all undefined (and some defined) properties unconditionally. Property hooks provide a more targeted, purpose-built tool for common property interactions.

class User 
{
    public string $name {
        set {
            if (strlen($value) === 0) {
                throw new ValueError("Name must be non-empty");
            }
            $this->name = $value;
        }
    }
 
    public function __construct(string $name) {
        $this->name = $name;
    }
}

You can play around with them on 3v4l.org

Click the bar to cast your vote!
61%
39%
36

This is good to have.

Properties are useful for exchanging (reading and writing) single values. Properties are good for data binding, etc.

With this RFC we can implement:

  • Validation
  • Trigger events
  • Call methods if there is more to do

Update: About the $field I am not sure. Having a separate backing field can have some advantages.

Share:
maz avatar
maz
voted yes
28

This kind of code looks very appealing when performing very simple (and anemic) CRUD operations on a model, but it has a very short trajectory when the code gets a little more complex.

Triggering events is shown as an example of an advantage of this feature, but it's clearly a bad idea: https://3v4l.org/ZCVpG#v8.2.11

Also, if you need a centralized validation around an attribute, a value object is a way better option: https://3v4l.org/QWm8c#v8.2.11

I think that there are a ton of better requests with a lot more of value to achieve this in userland code.

Also, this idea is very overlooked, and would require a lot of internals work after to cover all the edge cases. How should child classes behave? Are they overridable? How do you know if there is a setter behavior defined already? How should traits behave? Is there a way to call the parent class setter/getter? Would that be overriden or just called by default?

Also, in response to some comments:

It could be used by ORMs like Laravel Eloquent Model that has cast and other hooks to transform data on get/set.

Good ORMs should provide mechanisms for this!

However, I actually often find the need for more fine-grained control over input and output, but adding methods feels so heavy-weight.

This is the perfect case for value objects to kick in. You want behavior (input validation, output transformation maybe) at an atomic member, that could be exactly this but atomically standalone, and therefore reusable in other cases, and very easily testable.

Share:
devnix avatar
devnix
voted no
20

While I'm not against the concept in general, this implementation is not well-done. The $value variables comes from nowhere and makes it confusing - it looks like an undefined variable and a quick glance at it, was the first thing I thought - where is $value coming from? Why not just use $name within that block? Same with $field? Why not just return the value that is going to be set? This is one of the more confusing RFCs I've seen and does not follow the PHP-code style, makes things confusing for both new and experienced coders. In it's current format, I can't approve with good conscience.

Share:
jim avatar
jim
voted no
19

Nice addition, but I would change syntax:

class User 
{
    public string $name {
        set($value) {
            if (strlen($value) === 0) {
                throw new ValueError("Name must be non-empty");
            }
            return $value;
        }
    }
 
    public function __construct(string $name) {
        $this->name = $name;
    }
}
Share:
yoshi129 avatar
yoshi129
voted yes
17

In combination with Asymmetric visibility this will allow to replace all getters and setters with trivial properties and occasional hooks.

Share:
pronskiy avatar
pronskiy
voted yes
10

Thanks to readonly properties, I started relying more and more on properties alone. However, I actually often find the need for more fine-grained control over input and output, but adding methods feels so heavy-weight. Property hooks feels like the perfect solution for some of my use cases.

Share:
Contributor brent avatar
brent
voted yes
7

In my opinion the solution as is proposed is ambigious because you would have to guess the variable names and these variables by themselfs only suggest their functionality. Like other langauge constructs which have specific keywords or syntax. The concept I support but this variant I do not.

Share:
noah avatar
noah
voted no
6

There are some arguments against the implementation and I agree to those. But in addition I don't agree to the concept itself, personally, because I am convinced that immutability is key when enforcing domain logic. E.g. the example above could be replaced by:

readonly class User {
		public function __construct(public string $name) {
				if (strlen($name) === 0) {
						throw new ValueError('Name must be non-empty');
				}
		}

		public function withName(string $newName): self {
				return new self($newName);
		}
}

Or, even better IMO, with value objects that validate themselves:

readonly class User {
		public function __construct(public Name $name) {}

		public function withName(Name $newName): self {
				return new self($newName);
		}
}

readonly class Name {
		public function __construct(public string $value) {
				if (strlen($value) === 0) {
						throw new ValueError('Name must be non-empty');
				}
		}
}

Granted, this is only one example and immutabilty is not a silver-bullet. But I haven't come across a usecase for property hooks that was really convincing yet

Share:
bastian avatar
bastian
voted no
6

Moving to C# is not a good way IMHO. We will end with classes where properties are heavily mixed with the logic before actually we see class methods.

Share:
stevad avatar
stevad
voted no
6

C#-inspired properties, yes please!

Share:
yassine avatar
yassine
voted yes
5

It's like Accessors/Mutators in Laravel but native.

Share:
roman-3 avatar
roman-3
voted yes
3

Since I'm working also with Kotlin I desperatly crave for a feature like this. At least if it is as powerful as in Kotlin where I could e.g. change the setter to be protected or private while the getter can be public

Share:
eydamos avatar
eydamos
voted yes
3

It could be nice addition to creating DTOs objects in PHP with "virtual" properties:

readonly final class SomeDto
{
    public function __construct(
        public string $name,
        public string $surname,
        public string $fullName {
            get => ucfirst($this->name) . ' ' . ucfirst($this->surname);
        }
    ) {
    }
}

or even shorter version:

readonly final class SomeDto
{
    public function __construct(
        public string $name,
        public string $surname,
        public string $fullName => ucfirst($this->name) . ' ' . ucfirst($this->surname),
    ) {
    }
}
Share:
bronek89 avatar
bronek89
voted yes
2

$value magically appearing is not good.

Share:
sakarikl avatar
sakarikl
voted no
2

Love it. like in swift. If property observers could be added this would be awesome!

Share:
grandfelix avatar
grandfelix
voted yes

Check out another RFCs

The Pipe Operator

The "pipe operator" |> allows you to chain multiple function calls in a more convenient way.

93
272 yes
131 no
Asymmetric Visibility v2

A new syntax for declaring the “set” operation visibility of an object property

21
21 yes
38 no
new MyClass()->method() without parentheses

Chain method on newly created objects without parentheses

54
95 yes
16 no
RSS Feed Contribute Watch on YouTube Our License
© 2024 RFC Vote. This project is open source. Contribute and collaborate with us!