Recently when I was working on a project I got some strange results when using the empty function. Here's what I was debugging. I've simplified the code a bit to share with you.

var_dump(
   $person->firstName, 
   empty($person->firstName)
);

This was the result:

string(5) "Freek"
bool(true)

That's really odd. How can a variable hold a string and be empty at the same time? Let's try a few other functions on $person->firstName:

var_dump(
   $person->firstName, 
   empty($person->firstName), 
   isset($person->firstName), 
   is_null($person->firstName)
);

The result:

string(5) "Freek"
bool(true) // empty
bool(true) // isset
bool(false) // is_null

isset and is_null behave like expected, only empty returns the wrong result.

Let's take a look at the implementation of the Person class.

class Person
{
    protected $attributes = [];

    public function __construct(array $attributes)
    {
        $this->attributes = $attributes;
    }

    public function __get($name)
    {
        return $this->attributes[$name] ?? null;
    }
}

Here you can see that the properties on a Person object are retrieved from the $attributes array using that magic __get() method.

When passing variables to normal functions $person->firstName will be evaluated first. The result of that evaluation will get passed to the function.

But empty is not a function, it's a language construct. So what you pass to it will not be evaluated first. When passing $person->firstName to it, it will check the contents of the firstName property on $person. Since that property doesn't exist, it will return false.

If you want to make the empty function work in this case you'll need to implement the magic __isset method.

class Person
{
    protected $attributes = [];

    public function __construct(array $attributes)
    {
        $this->attributes = $attributes;
    }

    public function __get($name)
    {
        return $this->attributes[$name] ?? null;
    }

    public function __isset($name)
    {
        $attribute = $this->$name;

        return !empty($attribute);
    }
}

When empty does it's job it'll use that magic method to determine the result.

Let's try dumping again:

var_dump(
   $person->firstName, 
   empty($person->firstName)
);

The new results:

string(5) "Freek"
bool(false)

Perfect!