19

Example:

error_reporting(E_ALL | E_STRICT);
class Test {}
$obj = new Test();
$obj->undeclared = "oops";    // I want an error here !! :(
echo $obj->algo;              // oops 

I tested it on PHP 5.2.11 and 5.3.0.

I don't want dynamic properties in my objects.
Is possible to force PHP to raise an ERROR in that situation ?

Enrique
  • 4,241
  • 5
  • 41
  • 59
  • How `$obj->undeclared` is causing problem for you? – Shiplu Mokaddim Feb 03 '12 at 23:16
  • class Test{ public $myVar; } $obj->my_var = TRUE; /* typo */ if($obj->myVar) { /* oh oh ! */ } – Enrique Feb 03 '12 at 23:21
  • You need a good IDE for that to prevent typo like this. – Shiplu Mokaddim Feb 03 '12 at 23:25
  • can you tell me an IDE capable of doing that? I'm using Zend Studio (of course, you have code completion, but using Ctrl+Space for every property just because if you make a typo PHP won't tell you is an overkill) – Enrique Feb 03 '12 at 23:33
  • I use eclipse, netbeans. All does it properly. They are capable of getting property names from annotation too. – Shiplu Mokaddim Feb 03 '12 at 23:34
  • Upvoted the question because this caused me a lot of pain and wasted time today. PHP lets you use properties that don't exist when you are outside of the class. But if you are inside then class then it starts complaining. – srayner Jun 22 '17 at 19:28
  • PHP already has semantic arrays, which can be extended dynamically. Its objects should be fixed according to their class declarations, just like class static is fixed. ("Fixed" means non-dynamic here.) – David Spector Jun 27 '19 at 00:16
  • 1
    The situation is actually worse. If you change this example to read an undefined property instead of trying to write to it, you actually do get a "notice"-level error messsage: "Notice: Undefined property: Test::$B ...". So PHP actually treats undefined read references as errors and undefined write references as success. So, correct me if I'm wrong, but PHP is actually inconsistent and therefore has a bug. – David Spector Jun 27 '19 at 19:17

3 Answers3

19

Use __set() ?

<?php
class Test {

    public $bar;

    public function __set($name, $value) {
        throw new Exception('Cant set!');
    }
}

$obj = new Test;
$obj->bar = 'foo';
$obj->foo = 'evil';
?>
radmen
  • 1,466
  • 9
  • 13
  • 5
    Yes that will work, but then I need to change all my classes for adding that function, that's not good. This should be done natively by PHP. – Enrique Feb 03 '12 at 22:58
  • Nice trick!!!!! Checked that it "inherits normally", so I'll use this in my base classes!! :) – Xavi Montero Jan 09 '14 at 11:25
  • I think it might be good that the language let you decide what to do with assignment to non-existent or non-accesible properties. But I think it would be better if this was the default behaviour and then one could enable dynamic setting of properties for defined classes with some configuration option – Santi Jan 21 '21 at 14:10
  • @Enrique it's kinda emblematic of PHP's "looseness" that it allows this by default; that's to say, I for one having used PHP for a few years before touching classes, _expected_ that behaviour (YMMV). At least now you can put this answer's method in a Trait to be used/inherited by all the classes that you want it for. – Headbank Apr 27 '21 at 10:46
1

A bit more diligence with your error message and you can help your own coding. Having the following as your base class setup will:

  1. protect uses of this class from overwriting your properties
  2. protect misspellings of variables (especially camel-case mistakes) for instance $this->callerID returns as expected but $this->callerId throws an exception and prevents code errors.
    public function __set($name,$value) {
        switch ($name) {
            default:
                // we don't allow any magic properties set or overwriting our properties
                try {
                    $error = "Assignment of {$name} in " . static::class . ' not allowed because it is a magic variable or read-only property.';
                    throw new \RuntimeException($error);
                } catch ( \RuntimeException $e ) {
                    echo 'Caught exception: ' . $e->getMessage() . PHP_EOL;
                }

        }
    }

    public function __get($name)
    {
        switch ($name) {
            default:
                // we don't allow any magic properties
                try {
                    $error = "var {$name} is not a property of " . static::class . '.';
                    throw new \RuntimeException($error);
                } catch ( \RuntimeException $e ) {
                    echo 'Caught exception: ' . $e->getMessage() . PHP_EOL;
                }
        }
        return null;
    }

    public function __isset($name)
    {
        switch ($name) {
            default:
                return false;
        }
    }
0

By "I don't want dynamic properties in my objects." I assume you mean that you don't want your object variables to ever be undefined? If this is the case then just instantiate all of your class variables. You can even make them static if you're really concerned about them being constant as opposed to dynamic in any way.

If you attempt to access an object variable that does not exist, then PHP will throw an error. The answer that radmen suggested would actually make it so that instead of throwing an error, PHP would dynamically create undefined object variables for you on the fly.

David Myers
  • 799
  • 1
  • 8
  • 18
  • No. I want an Error/Notice/Warning when I'm trying to set a property that is not declared in my class (that is, I don't want dynamic properties in my objects, if is not declared in my class then it should not exists, never). If I want that, then I will use __set and __get. – Enrique Feb 03 '12 at 23:02
  • Well, PHP does do exactly what you're looking for. You might need to add E_WARNING to your error reporting for it to be displayed though. – David Myers Feb 03 '12 at 23:07
  • 2
    no, it doesn't, and if you read my example you'll see I'm adding E_ALL which includes E_WARNING (and I'm also adding E_STRICT because is the only not included in E_ALL, at least prior 5.4) – Enrique Feb 03 '12 at 23:24
  • Sorry, I was making assumptions that seemed logical, but you are right. After looking into it more, Shiplu proposed the best solution, using __set() to report an error. However, it doesn't necessarily need to be in a base class and you can have it actually throw an exception if you'd like. – David Myers Feb 04 '12 at 01:14