PHP Warning Deprecated: Creation of dynamic property is deprecated
Asked Answered
W

9

41

I'm seeing this more and more, and I'm not sure what I need to do to stop this warning:

Deprecated: Creation of dynamic property ... is deprecated

This is my class:

class database {

    public $username = "root";
    public $password = "password";
    public $port = 3306;

    public function __construct($params = array())
    {
        foreach ($params as $key => $value)
        {
            $this->{$key} = $value;
        }
    }
}

This is how I'm instantiating it.

$db = new database(array(
    'database' => 'db_name',
    'server' => 'database.internal',
));

Which gives me two messages:

Deprecated: Creation of dynamic property database::$database is deprecated

Deprecated: Creation of dynamic property database::$server is deprecated

Wideawake answered 21/12, 2022 at 16:18 Comment(9)
What's the exact warning you're getting?Federate
I get a couple of them like this, "Unknown error type: [8192] Creation of dynamic property database::$server is deprecated and one for $server also"Wideawake
There has to be additional parameters that you're passing in later on when you're creating that $database object.Federate
DOH. you're right. I'll update it from $database = new Database(); to what I'm using which is $db = new database(array( 'database' => 'db_name', 'server' => 'database.internal', ));Wideawake
Using $this->{$key} is a risky solution (IMHO) as it's easy (like you've found) to get the parameters wrong.Xyloid
You might precede the setter with something like: if (!property_exists($this, $key)) { throw new Exception('Unknown property'); } It won't fix your code, but it will at least give you a hook into the process of nailing down your other violations.Grider
@AlexHowansky The message already names the property that's being created; the OP just edited it out.Choosey
@Choosey The recommendation wasn't about determining the name of the property, it was about being able to gracefully trigger a remediation process when it inevitably happens again in your legacy codebase. E.g., you might log a backtrace, ping OpsGenie, whatever.Grider
@AlexHowansky Well, you can do that just by having good logging of deprecation notices. The main difference is that the deprecation notice is designed to notify you and keep the behaviour of previous PHP versions (create a public property on the fly), whereas throwing an exception will be closer to the behaviour of a future PHP version (abort the operation with a catchable error).Choosey
F
18

So the warnings are coming from the constructor adding dynamic class properties. If you don't have to pass in those fields dynamically and really, it does seem like you're overcomplicating something simple then try it like this.

class database {

    public $username = "root";
    public $password = "pasword";
    public $port = 3306;
    public $database = 'db_name';
    public $server = 'database.internal';
}


$db = new database();

Is there a reason you needed dynamic parameters? You could also do this:

class database {

    public $username = "root";
    public $password = "pasword";
    public $port = 3306;
    public $database;
    public $server;

    public function __construct($params = array())
    {

        foreach ($params as $key => $value)
        {
            $this->{$key} = $value;
        }
    }
}

If you add the parameters ahead of time, they're not dynamic, and you're just assigning a value to something already existing.

This should work now without any warnings.


$db = new database(array(
    'database' => 'db_name',
    'server' => 'database.internal',
));

Federate answered 21/12, 2022 at 16:31 Comment(5)
This also has problems of This would definitely be a solution but it would impede code completionXyloid
@NigelRen it does? It added $db->database and $db->server to the code completion options.Wideawake
@Johnny3653925, so when using new database() it helps with code completion?Xyloid
@NigelRen ya, when I create an instance of $db = new database(), I can then do $db-> and my ide shows me all my public members.Wideawake
@Johnny3653925 sounds like a coding mirror. oh man, sorry bad joke.Federate
C
46

The warning is telling you that there is a property you are trying to set which isn't declared in the class or any of its parents.

When you run this:

class database {

    public $username = "root";
    public $password = "pasword";
    public $port = 3306;

    public function __construct($params = array())
    {
        foreach ($params as $key => $value)
        {
            $this->{$key} = $value;
        }
    }
}

$db = new database(array(
    'database' => 'db_name',
    'server' => 'database.internal',
));

It is roughly equivalent to this:

class database {

    public $username = "root";
    public $password = "pasword";
    public $port = 3306;
}

$db = new database;
$db->database = 'db_name';
$db->server = 'database.internal';

The warning is that there is no line in the class definition saying that $db->database or $db->server exist.

For now, they will be dynamically created as untyped public properties, but in future, you will need to declare them explicitly:

class database {
    public $database;
    public $server;
    public $username = "root";
    public $password = "pasword";
    public $port = 3306;

    public function __construct($params = array())
    {
        foreach ($params as $key => $value)
        {
            $this->{$key} = $value;
        }
    }
}

$db = new database(array(
    'database' => 'db_name',
    'server' => 'database.internal',
));

In some rare situations, you actually want to say "the properties of this class are whatever I decide to add at run-time"; in that case, you can use the #[AllowDynamicProperties] attribute, like this:

#[\AllowDynamicProperties]
class objectWithWhateverPropertiesIWant {
    public function __construct($params = array())
    {
        foreach ($params as $key => $value)
        {
            $this->{$key} = $value;
        }
    }
}
Choosey answered 21/12, 2022 at 17:10 Comment(6)
I understand why the committee chose to do this, but in my opinion, once this is finally deprecated and starts throwing actual errors, it will blow up more applications than any other change that’s ever been made to PHP. They really should have thought about doing the opposite, and making the option available to explicitly deny, rather than allow, dynamically created properties.Empress
@DanS Funnily enough, that's exactly what I proposed a few years ago, but the argument was made that in the long term, having classes locked by default will be better for new users of the language. As is often the case, I can see both arguments. (Incidentally, "committee" is a rather grand term for the PHP Internals community; it's very loosely organised and ground-up, for better or worse.)Choosey
Note to new users. This is not the place to share your opinions of how horrible/surprising/great/confusing you find this feature/PHP/the 21st century. Comments are for suggesting clarifications or amendments to the content of a Question or Answer. See the tour and help center.Choosey
there should be a slash at the beginning: #[\AllowDynamicProperties] php.net/manual/en/class.allowdynamicproperties.phpSusurrate
@99Problems-Syntaxain'tone That depends a) what namespace you're in, and b) whether you have use AllowDynamicProperties; at the start of the file - in other words, it follows the same rules as any other class you mention. But as far as I know it never hurts to make it over-qualified, so in the interest of simple copy-and-paste, I've added it in the example.Choosey
PHP is morphing into C++Suki
F
18

So the warnings are coming from the constructor adding dynamic class properties. If you don't have to pass in those fields dynamically and really, it does seem like you're overcomplicating something simple then try it like this.

class database {

    public $username = "root";
    public $password = "pasword";
    public $port = 3306;
    public $database = 'db_name';
    public $server = 'database.internal';
}


$db = new database();

Is there a reason you needed dynamic parameters? You could also do this:

class database {

    public $username = "root";
    public $password = "pasword";
    public $port = 3306;
    public $database;
    public $server;

    public function __construct($params = array())
    {

        foreach ($params as $key => $value)
        {
            $this->{$key} = $value;
        }
    }
}

If you add the parameters ahead of time, they're not dynamic, and you're just assigning a value to something already existing.

This should work now without any warnings.


$db = new database(array(
    'database' => 'db_name',
    'server' => 'database.internal',
));

Federate answered 21/12, 2022 at 16:31 Comment(5)
This also has problems of This would definitely be a solution but it would impede code completionXyloid
@NigelRen it does? It added $db->database and $db->server to the code completion options.Wideawake
@Johnny3653925, so when using new database() it helps with code completion?Xyloid
@NigelRen ya, when I create an instance of $db = new database(), I can then do $db-> and my ide shows me all my public members.Wideawake
@Johnny3653925 sounds like a coding mirror. oh man, sorry bad joke.Federate
P
14

Adding AllowDynamicProperties just above the class will make the warnings go away:

#[\AllowDynamicProperties]
class database {
Plosion answered 25/1, 2023 at 16:53 Comment(6)
In this instance, I wouldn't call that a "fix". It will make the warnings go away, but the warnings are telling you something useful. If the class expects $database and $server properties, it should define them, and then they won't be "dynamic", and there will be no warning.Choosey
@Choosey can you tell me how is this useful?Melgar
@RandomDude This isn't really the place to discuss, but I can think of many reasons; for instance, in no particular order: 1) Catching typos: if you write $this->databsae = new DB; instead of $this->database = new DB; it's generally more helpful to get an error, than have the language runtime silently create an extra property, so that now you have both "database" and "databsae" defined. 2) Giving the property visibility (private or protected) and type. 3) "Advertising" to readers (including yourself in 6 months time) what properties exist, and let IDEs auto-complete them.Choosey
@Choosey what if you don't know which attribute will be sent from the list of 150?Melgar
@RandomDude That would definitely be an unusual situation - and notably very different from the one shown on this question. For that situation, adding the attribute would make sense - I didn't say it should never be used, just that it wasn't appropriate in this instance. On the other hand, it might still be better to store the properties in a single array property, with generic get/set methods, or magic __get and __set that had some rules for access and type checks without listing all 150 properties.Choosey
Is that just in the IDE?Wideawake
X
4

An alternative solution, which is specific to this example in that the parameters are quite easy to identify. Databases have common parameters that are referenced.

Using PHP 8 named parameters and Constructor property promotion, allows you to specify all of the possible parameters in the constructor (which is a bit long winded, but great for code completion) and then when creating an instance, the parameter names are fixed...

class database
{
    public function __construct(
        private string $database = '',
        private string $server = '',
        private string $port = '',
        private string $user = '',
        private string $password = ''
    ) {
    }
}

Then call with

$db = new database(
    database: 'db_name',
    server: 'database.internal',
);
Xyloid answered 21/12, 2022 at 16:44 Comment(6)
If I do this, then can I can't access the values of private variables from outside the class. I'll have to add some accessors for each one.Wideawake
@Johnny3653925, you can make them public if you want, but this is generally against OO practices as it allows other classes to mess with the values you have. (#4362053 has some info)Xyloid
ok, that's a much bigger picture to think about. I'll do some more research.Wideawake
Calling something an "alternative solution" is a bit confusing on this site, because the order of answers can change and new ones be added. Ideally, the answer should stand alone, and explain what the problem is, and how this solves it.Choosey
@IMSoP, it's an alternative solution in that it doesn't use dynamic properties, which is what OP is trying to use. Nothing to do with any other answers.Xyloid
Oh, in that case, I definitely think it could be clearer, because my immediate reaction was "alternative to what?" Since some people have completely misinterpreted the error message, it would be good to explain the actual problem as well as the solution.Choosey
C
4

This has been coming up endlessly on tons of plugins, systems, modules, ect...

The solution is quite simple: just declare the PARAMS you need in the class first. Then you can assign them later. For example:

class foo {

public $bar = "";  // my param BAR is declared here

public function __construct(){
       $this->bar = "no_error"; // this assignment of value to BAR will not trigger deprecation warning
      }
 }

^ No errors occur because the param is not "dynamically assigned" (aka: made up on the fly)

Lets now consider the deprecation warning itself using your sample:

class database {

public $username = "root";
public $password = "password";
public $port = 3306;

  public function __construct($params = array()){
    foreach ($params as $key => $value) { $this->{$key} = $value;}
    // you want this ^ to magically make properties... clever, but lazy & dangerous

    // these [below] would also fail here, they are declared on the fly...
    $this->databaseName= "myDB";
    $this->hostName= "some.mysql.server";
    $this->sqlQuery= "SELECT * FROM something";
   }
}

Thats going to land you these

# Deprecated: Creation of dynamic property database::databaseName is deprecated    
# Deprecated: Creation of dynamic property database::hostName is deprecated
# Deprecated: Creation of dynamic property database::sqlQuery is deprecated

In PHP 7 the class ya got there was supposed to make life easy by letting you instance then just shove-in some params that you can define on the fly, however, this is poor planning in php 8.2 (and in theory not very secure).

For details on that here is a TLDR; that is worth reading - https://php.watch/versions/8.2/dynamic-properties-deprecated

to make it simple? Just declare your params, like this:

class database {

public $username = "root";
public $password = "password";
public $port = 3306;

// just add params here, all planned out
public $databaseName = "";
public $hostName = "";
public $sqlQuery = "";

   public function __construct($params = array()){
    // now you do not need the offending dynamic foreach assignment here

    // and now you CAN do this with no error
    $this->databaseName = "myDB";
    $this->hostName = "some.mysql.server";
    $this->sqlQuery = "SELECT * FROM something";

    // or, you could grab them from the construct array you passed in
    $this->databaseName = $params['databaseName']; 
    $this->hostName = $params['hostName ']; 
    $this->sqlQuery = $params['sqlQuery ']; 
   }
}

If you really need the 'dynamic creation foreach loop' then other answers here will suffice.

IMHO: remove that foreach and just drop in your params, declare them in the class as expected in PHP 8.2 => done.

PS: I think its a bad idea to use #[AllowDynamicProperties] because you are going to have to correct the issue eventually... you could just shove that into every class that has the deprecation warning but is that really good dev?

Curr answered 28/12, 2023 at 9:12 Comment(0)
T
3

You can avoid the need to specify an explicit list of member variables by storing them in a single array, like this:

class database {
    public $data;

    public function __construct($params = array())
    {
        foreach ($params as $key => $value)
        {
            $this->data[$key] = $value;
        }
    }
}

Then you can instantiate a database object with any params you want:

$db = new database(array(
    'database' => 'db_name',
    'server' => 'database.internal',
    'foo' => 'bar', // etc.
));

Of course, you'll probably want to document what the expected params are, but at least you'll be able to pass in anything you want, and be able to use it in the future, without changing the class definition.

Trouvaille answered 21/12, 2022 at 16:58 Comment(1)
This would definitely be a solution but it would impede code completionWideawake
L
2

The warning message you are seeing is related to the use of a feature in PHP called "dynamic properties". Dynamic properties allow you to set and get object properties by using variable names, like you are doing in the __construct method of your database class.

Quick fix:

public function __construct(array $params)
{
    $this->username = $params['username'] ?? null;
    $this->password = $params['password'] ?? null;
    $this->port = $params['port'] ?? null;
}

To fix this warning, you can remove the use of dynamic properties in your code and use a more modern way of setting object properties.

Named arguments:

class database
{
    public function __construct(
        public string $username,
        public string $password,
        public int $port,
    ) {}
}

$db = new database(
    username: 'root',
    password: 'password',
    port: 3306,
);

Alternatively, you can also use the __set and __get magic methods to set and get object properties, like this:

public function __set($key, $value)
{
    $this->$key = $value;
}

public function __get($key)
{
    return $this->$key;
}

This will allow you to use the $object->property notation to set and get object properties, without triggering the warning message.

Lingwood answered 21/12, 2022 at 16:55 Comment(3)
I changed it from {$key} to just $key but still seeing the same warning.Wideawake
There is a slight misunderstanding; "dynamic properties" in this case doesn't refer to accessing object properties using variables for the name, it refers to getting or setting properties which haven't been declared. $propName='id'; $this->{$propName}=42; is fine as long as $id is declared at the top of the class; $this->id=42; will give the warning if $id is not declared at the top of the class.Choosey
Just to reiterate for the people voting this up, this answer is misleading, because it is using the wrong definition of dynamic properties. Writing $this->{$key} = $value is not what is triggering the notice, writing $this->database - $value would trigger exactly the same message.Choosey
F
2

if you only want the warnings not to be displayed, simply replace this:

class database {

for this:

class database extends \stdClass {
Familiarity answered 8/2, 2023 at 2:16 Comment(2)
In this instance, I don't think this is the right solution. It will make the warnings go away, but the warnings are telling you something useful. If the class expects $database and $server properties, it should define them, and then they won't be "dynamic", and there will be no warning.Choosey
@Choosey you're right!Familiarity
W
0

you can simply put a filter before assigning non default values in the constructor:

class database {

    public $username = "root";
    public $password = "password";
    public $port = 3306;

    public function __construct($params = array())
    {
        foreach ($params as $key => $value)
        {
           if(property_exists($this, $key)) {
                 $this->{$key} = $value;
           }
            
        }
    }
}

This will also ensure that no one adds additional properties to the class

Walcott answered 26/2 at 7:5 Comment(2)
That assumes existing properties are non-static and can be assigned a new value. You should first check that the property you're trying to assign to is not static. https://mcmap.net/q/392590/-possible-to-test-if-a-variable-is-static-in-phpTrouvaille
this doesn't really solve the problem just makes more problems with the existing code but thanks anyway for trying.Wideawake

© 2022 - 2024 — McMap. All rights reserved.