Property-Hooks were one of the really hot topics of last years PHP8.4 release. And I do have very strong opinions on them. They were and probably still are extremely hyped for something that in my personal opinion should be a very niche thing. In new projects you shouldn’t really need them.
But just today I had one of those situations where they helped me combine legacy code with new functionality.
The starting position
I am working on a project where we are using PDO and especially PDO::FETCH_CLASS to create Objects from our database-content.
So the code looks something like this:
function getAllFoos() : array
{
$stmt = $this->executePdoQuery(<<<SQL
SELECT * FROM table
SQL);
$stmt->setFetchMode(PDO::FETCH_CLASS, Foo::class);
return $stmt->fetchAll();
}
Foo looks something like this:
final class Foo
{
public string a;
public string b;
}
And the database-table looks something like this:
CREATE TABLE foo (
a VARCHAR(20),
b VARCHAR(20),
)
Everything worked as expected and calling getAllFoos got me a list of Foo-objects.
So Far so good.
Now I wanted to add a new field to the Foo-object and that should contain an Enum. Easy peasy.
final class Foo
{
public string a;
public string b;
private Status $status;
}
enum Status: string {
case Open = 'open';
case Closed = 'closed';
}
Now I need to update the database
ALTER TABLE foo ADD COLUMN `status` VARCHAR(10) NOT NULL DEFAULT 'open';
That’s it!
When testing that though I found that it wasn’t because now everything blew up when calling getAllFoos ….
What happened?
Well, as expected the fetchAll tried to hydrate the values of the Foo object wth the values from the DB. But the DB contains a string whereas the Foo object expects the respective enum.
There is no out of the box solution to this. PDO has no idea how to convert this database-value into something the object understands (it could infer the expected enum-type from the type-hint but that can get out of hand rather quickly when it’s not an enum bot any other object that doesn’t have a ::from method or…)
Property Hooks to the rescue.
So I had to find my own way around this. And here property hooks are awesome when one has to adapt to legacy code.
So this is what my Foo class now looks like
final class Foo
{
public string a;
public string b;
private Status $status {
set(string|Status $value) {
if (is_string($value)) {
$value = Status::from($value);
}
$this->status = $value;
}
}
}
Works like a charm now!