PHP:阻止在 class 之外创建实例变量

PHP: deterring instance variable creation outside the class

在下面给定的 PHP class 中,我了解到 $test 将无法从 class.

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

但是我们将能够定义新的实例变量,如下所示。有什么技巧可以阻止它吗?

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

使用魔术方法(namly __set)你可以告诉 class "if this is not set in here, ignore it",例如;

<?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

参考请看the guide

将数据写入不可访问的属性时调用 'magic method' __set(如果已声明)。

您可以利用它来防止创建未声明的属性:

<?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';

输出:

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

如您所见,当尝试写入未声明的 属性.

时,上面的代码会抛出异常

如果您尝试写入另一个不可访问的 属性(在类似范围内),如上面的 Foo::baz(声明为私有),调用将通过 __set 魔法路由法也。如果没有 __set 方法,这将导致致命错误。

但是,对 Foo::bar 的写入(上面声明的 public)将不会通过 __set 魔术方法进行路由。

如果你想在许多 类 中强制执行这种类型的严格行为,你可以将以上内容作为特征来实现:

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

class Bob
{
    use Upsetter;
}

相当安全的解决方案。您应该避免使用那些 __set 方法,因为它们不关心 private/protected 属性。使用 class 反射,查看 属性 是否为 public 并且可用于 __set。下面的小例子。

    <?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;