Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP 7.4 Typed properties iteration

I just found something "kinda strange" about PHP 7.4 and I am not sure if it is just me missing something or maybe if it is an actual bug. Mostly I am interested in your opinion/confirmation.

So in PHP, you can iterate over objects properties like this:

class DragonBallClass 
{
    public $goku;
    public $bulma = 'Bulma';
    public $vegeta = 'Vegeta';
}

$dragonBall = new DragonBallClass();

foreach ($dragonBall as $character) {
  var_dump($character);

}

RESULT

NULL
string(5) "Bulma"
string(6) "Vegeta"

Now if we start using strongly typed properties like that:

class DragonBallClass 
{
    public string $goku;
    public string $bulma = 'Bulma';
    public string $vegeta = 'Vegeta';
}

$dragonBall = new DragonBallClass();

foreach ($dragonBall as $character) {
  var_dump($character);

}

We will get a different result:

string(5) "Bulma"
string(6) "Vegeta"

Now what is different:

When you DO NOT assign a default value to strongly typed property it will be of Uninitialized type. Which of course makes sense. The problem is though that if they end up like this you cannot loop over them they will simply be omitted - no error, no anything as you can see in the second example. So I just lose access to them.

It makes sense but just imagine that you have a custom Request/Data class like this:

namespace App\Request\Request\Post;

use App\Request\Request\Request;

class GetPostsRequest extends Request
{
    public string $title = '';
}

Do you see that ugly string assignment? If I want to make my properties on the class iterable then I have to either:

  • drop types
  • assign dummy values

I might want to have an object with typed properties without any values in them to loop over them and populate them if that makes sense.

Is there any better way of doing this? Is there any option to keep types and keep em iterable without having to do this dummy value abomination?

like image 335
Robert Avatar asked Mar 29 '20 12:03

Robert


People also ask

What is typed property in PHP?

With typed properties, you can set a type for all class properties. When a type is set, PHP engine prevents anyone from setting a different type to the class property. The snippet above will make sure that Example::$birthday property will always be a DateTime object.

What is method and properties in PHP?

PHP - Access Modifiers public - the property or method can be accessed from everywhere. This is default. protected - the property or method can be accessed within the class and by classes derived from that class. private - the property or method can ONLY be accessed within the class.

How do you access the properties of an object in PHP?

Within class methods non-static properties may be accessed by using -> (Object Operator): $this->property (where property is the name of the property). Static properties are accessed by using the :: (Double Colon): self::$property .

How do you access properties and methods Explain with example and also explain $this variable in PHP?

For example, you can access the $name property by using $this->name (note that you don't use a $ before the name of the property). An object's methods can be accessed in the same way; for example, from inside one of person's methods, you could call getName() by writing $this->getName().

What is the typed property change in PHP 7?

The typed property change is a PHP 7.4 proposal. With the introduction of scalar types and return types, PHP 7 greatly increased the power of PHP’s type system. However, it is currently not possible to declare types for class properties, forcing developers to use getter and setter methods instead to enforce type contracts.

Why do we need a 2/3 majority for typed properties in PHP?

A 2/3 majority is required because typed properties is a language change. The typed property change is a PHP 7.4 proposal. With the introduction of scalar types and return types, PHP 7 greatly increased the power of PHP’s type system.

Can We declare types for class properties in PHP?

With the introduction of scalar types and return types, PHP 7 greatly increased the power of PHP’s type system. However, it is currently not possible to declare types for class properties, forcing developers to use getter and setter methods instead to enforce type contracts.

What is the purpose of typed properties in PHP?

The purpose of typed properties is to avoid implicit initialization and always provide an explicit value that is clear and makes sense. And please don't just define all properties as nullable to make this exception disappear!! as this will defeat the whole point of typed properties and PHP 7.4 .


3 Answers

If you want to allow a typed attribute to be nullable you can simply add a ? before the type and give NULL as default value like that:

class DragonBallClass 
{
    public ?string $goku = NULL;
    public string $bulma = 'Bulma';
    public string $vegeta = 'Vegeta';
}

In this case NULL is a perfectly legitimate value (and not a dummy value).

demo


Also without using ?, you can always merge the class properties with the object properties lists:

class DragonBallClass 
{
    public string $goku;
    public string $bulma = 'Bulma';
    public string $vegeta = 'Vegeta';
}

$dragonBall = new DragonBallClass();

$classProperties = get_class_vars(get_class($dragonBall));
$objectProperties = get_object_vars($dragonBall);

var_dump(array_merge($classProperties, $objectProperties));

// array(3) {
//  ["goku"]=>
//  NULL
//  ["bulma"]=>
//  string(5) "Bulma"
//  ["vegeta"]=>
//  string(6) "Vegeta"
// }
like image 183
Casimir et Hippolyte Avatar answered Oct 23 '22 15:10

Casimir et Hippolyte


Before we start - I think that the answer accepted by me and provided by Casimir is better and more correct than what I came up with(that also goes for the comments).

I just wanted to share my thoughts and since this is a working solution to some degree at least we can call it an answer.

This is what I came up with for my specific needs and just for fun. I was curious about what I can do to make it more the way I want it to be so don't freak out about it ;P I think that this is a quite clean workaround - I know it's not perfect though.

class MyRequest
{
    public function __construct()
    {    
        $reflectedProperties = (new \ReflectionClass($this))->getProperties();
        foreach ($reflectedProperties as $property) {
            !$property->isInitialized($this) ??
            $property->getType()->allowsNull() ? $property->setValue($this, null) : null;
        }
    }

}


class PostRequest extends MyRequest 
{
    public ?string $title;

}

$postRequest = new PostRequest();

// works fine - I have access here!
foreach($postRequest as $property) {
    var_dump($property);
}

The downfall of this solution is that you always have to make types nullable in your class. However for me and my specific needs that is totally ok. I don't mind, they would end up as nullables anyway and it might be a nice workaround for a short deadline situation if someone is in a hurry.

It still keeps the original PHP not initialized error though when the type is not nullable. I think that is actually kinda cool now. You get to keep all the things: Slim and lean classes, PHP error indicating the true nature of the problem and possibility to loop over typed properties if you agree to keep them nullable. All governed by native PHP 7 nullable operator.

Of course, this can be changed or extended to be more type-specific if that makes any sense.

like image 33
Robert Avatar answered Oct 23 '22 15:10

Robert


Update: this answer may be obsolete, but the comments contain an interesting discussion.

@Robert's workaround is buggy; in this part:

        foreach ($reflectedProperties as $property) {
            !$property->isInitialized($this) ??
            $property->getType()->allowsNull() ? $property->setValue($this, null) : null;
        }

the ?? must be corrected to &&.

Moreover that's a misuse of the ternary conditional; just use a classic if:

        foreach ($reflectedProperties as $property) {
            if (!$property->isInitialized($this)
                && $property->getType()->allowsNull()
            ) {
                $property->setValue($this, null);
            }
        }

or:

        foreach ($reflectedProperties as $property) {
            if (!$property->isInitialized($this) && $property->getType()->allowsNull()) {
                $property->setValue($this, null);
            }
        }
like image 27
user13852734 Avatar answered Oct 23 '22 14:10

user13852734