15

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

ADyson
  • 57,178
  • 14
  • 51
  • 63
Johnny 3653925
  • 349
  • 1
  • 3
  • 10
  • What's the exact warning you're getting? – jbrahy Dec 21 '22 at 16:19
  • 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" – Johnny 3653925 Dec 21 '22 at 16:21
  • There has to be additional parameters that you're passing in later on when you're creating that $database object. – jbrahy Dec 21 '22 at 16:23
  • 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', )); – Johnny 3653925 Dec 21 '22 at 16:24
  • Using `$this->{$key}` is a risky solution (IMHO) as it's easy (like you've found) to get the parameters wrong. – Nigel Ren Dec 21 '22 at 16:27
  • 1
    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. – Alex Howansky Dec 21 '22 at 16:31
  • 1
    @AlexHowansky The message already names the property that's being created; the OP just edited it out. – IMSoP Dec 21 '22 at 17:12
  • @IMSoP 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. – Alex Howansky Dec 21 '22 at 17:25
  • @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). – IMSoP Dec 21 '22 at 17:29

7 Answers7

18

The warning is telling you that there is a property you are trying to set which isn't listed at the top of the class.

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;
        }
    }
}
IMSoP
  • 89,526
  • 13
  • 117
  • 169
  • 1
    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. – Dan S Jun 13 '23 at 17:23
  • @DanS Funnily enough, that's exactly what [I proposed a few years ago](https://wiki.php.net/rfc/locked-classes), 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.) – IMSoP Jun 13 '23 at 19:12
11

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',
));

jbrahy
  • 4,228
  • 1
  • 42
  • 54
4

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

#[\AllowDynamicProperties]
class database {
Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 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. – IMSoP Apr 18 '23 at 14:46
2

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',
);
Nigel Ren
  • 56,122
  • 11
  • 43
  • 55
  • 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. – Johnny 3653925 Dec 21 '22 at 17:01
  • @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. (https://stackoverflow.com/questions/4361553/what-is-the-difference-between-public-private-and-protected has some info) – Nigel Ren Dec 21 '22 at 17:03
  • ok, that's a much bigger picture to think about. I'll do some more research. – Johnny 3653925 Dec 21 '22 at 17:06
  • 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. – IMSoP Dec 21 '22 at 17:18
  • @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. – Nigel Ren Dec 21 '22 at 18:12
  • 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. – IMSoP Dec 21 '22 at 18:44
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.

Gaios
  • 67
  • 4
  • I changed it from {$key} to just $key but still seeing the same warning. – Johnny 3653925 Dec 21 '22 at 17:00
  • 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. – IMSoP Dec 21 '22 at 17:06
  • 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**. – IMSoP Dec 21 '22 at 18:42
2

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.

kmoser
  • 8,780
  • 3
  • 24
  • 40
0

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

class database {

for this:

class database extends \stdClass {
Ale DC
  • 1,646
  • 13
  • 20
  • 1
    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. – IMSoP Apr 18 '23 at 14:47
  • @IMSoP you're right! – Ale DC Apr 18 '23 at 15:27