Aanand Natarajan
Aanand Natarajan

Reputation: 109

PHP: deterring instance variable creation outside the class

In the given PHP class below, I understand that $test will not be accessible from outside this class.

class Resource {
    private $test;
    public function __construct() {
       echo "in init";
       $test = "new val";
    }
}

But we will be able to define, new instance variables as below. Is there a trick to stop this?

$r = new Resource();
$r->val = "value"; 

Upvotes: 2

Views: 207

Answers (3)

Progrock
Progrock

Reputation: 7485

The 'magic method' __set (if declared) is called when writing data to inaccessible properties.

You can use this to your advantage to prevent the creation of undeclared properties:

<?php
class Foo
{
    public  $bar;
    private $baz;

    public function __set($name, $value)
    {
        throw new Exception('Strictly no writes to inaccesible and undeclared properties.');
    }
}

$f = new Foo;
$f->bar = 'hello';
$f->qux = 'earth';

Output:

Fatal error: Uncaught Exception: Strictly no writes to inaccesible and undeclared properties. in /var/www/stackoverflow/so-tmp-f2.php:20 Stack trace: #0 /var/www/stackoverflow/so-tmp-f2.php(26): Foo->__set('qux', 'earth') #`1 {main} thrown in /var/www/stackoverflow/so-tmp-f2.php on line 20

As you can see, the above will throw an exception when trying to write to an undeclared property.

If you try to write to another inaccessible property (in a similar scope) like Foo::baz above (which is declared private), calls will be routed through the __set magic method also. Without the __set method this will result in a Fatal Error.

However, a write to Foo::bar (declared public above) will NOT be routed through the __set magic method.

If you want to enforce this type of strict behaviour in many classes you could implement the above as a trait:

trait Upsetter
{
    public function __set($name, $value)
    {
        throw new Exception('Strictly no writes to inaccesible and undeclared properties.');
    }
}

class Bob
{
    use Upsetter;
}

Upvotes: -1

Eakethet
Eakethet

Reputation: 682

Quite safer solution. You should avoid doing those __set methods, because they dont care about private/protected properties. Use class reflection, to see, if property is public and accessible for __set. Small example bellow.

    <?php

class Resource {
    private $test = 55;
    public $foo;
    public $bar;

    public function __set($name, $value)
    {
        if(isset($this->{$name})) {
            $reflection = new ReflectionObject($this);
            $properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
            $isPublic = false;
            /** @var ReflectionProperty $property */
            foreach ($properties as $property) {
                if ($property->getName() == $name) {
                    $isPublic = true;
                    break;
                }
            }
            if ($isPublic) {
                $this->{$name} = $value;
            } else {
                //is private, do not set
                echo 'Im private, dont touch me!';
            }
        } else {
            // not here
            echo 'Im not here';
        }
    }
}

$resource = new Resource();
$resource->test = 45;

Upvotes: 1

anon
anon

Reputation:

Using magic methods (namly __set) you can tell the class "if this is not set in here, ignore it", for instance;

<?php

class Resource {
    private $test;
    public function __construct() {
       echo "in init";
       $this->test = "new val";
    }

    public function __set($name, $val)
    {
        // If this variable is in the class, we want to be able to set it
        if (isset($this->{$name})
        {
            $this->{$name} = $val;
        }
        else
        {
            // Do nothing, add an error, anything really
        }
    }
}

$resource = new Resource();
$resource->foo = "bar";
echo $resource->foo; // Will print nothing

For reference, please see the guide

Upvotes: 1

Related Questions