上学时,老师一定说过,类的private成员变量不能修改和获取,想要这么做需要添加public的getter和setter方法:
class Student { public $name = 'Daniel'; public $age = 18; private $secret = '我要炸学校'; // 这是秘密 public function getSecret() { return $this->secret; } public function setSecret($secret) { $this->secret = $secret; } }
这样做完全没问题,但是一个人的秘密怎么能随便告诉别人、随便修改呢,所以出于安全考虑,在设计类的时候有些private属性就没有setter和getter,秘密怎么能让别人知道和修改,这就不叫秘密了。所以往往是这样的情况:
class Student { public $name = 'Daniel'; public $age = 18; private $secret = '我要炸学校'; // 这是秘密 }
使用Reflection
作为一个好学生,一直谨记着老师的教诲,私有的是不能拿来用的,现实生活中也同样如此,别人的东西要经过允许才能用。
但当我这两天接触了Reflection后,发现它能获取成员变量并且修改它。
class People { public $name = 'Foo'; protected $age = 18; public function __construct($name,$age) { $this->name = $name; $this->age = $age; } public function toString() { echo "Name: $this->name, Age: $this->age" . PHP_EOL; } } class Student extends People { private $secret = 'I love Fiona.'; protected $major; public function __construct($name, $age, $major) { $this->major = $major; parent::__construct($name, $age); } public function toString() { echo "Name: $this->name, Age: $this->age, Major: $this->major" . PHP_EOL; } } $student = new Student('Daniel',20,'CS'); $classInfo = new ReflectionClass(Student::class); $nameProperty = $classInfo->getProperty('name'); // Daniel echo $nameProperty->getValue($student);
对于public可以任意获取,但是对protected和private会报如下错误:
$classInfo = new ReflectionClass(Student::class); $majorProperty = $classInfo->getProperty('major'); echo $majorProperty->getValue($student); // 报错如下 PHP Fatal error: Uncaught ReflectionException: Cannot access non-public member Student::major
这和课本上教的一样,但代码稍作修改就能获取到了:
$classInfo = new ReflectionClass(Student::class); $majorProperty = $classInfo->getProperty('major'); $majorProperty->setAccessible(true); echo $majorProperty->getValue($student) . PHP_EOL; // CS $secretProperty = $classInfo->getProperty('secret'); $secretProperty->setAccessible(true); // 设置成可访问 echo $secretProperty->getValue($student) . PHP_EOL; // I love Fiona. $secretProperty->setValue($student,'hahaha'); echo $secretProperty->getValue($student) . PHP_EOL; // hahaha
畅行无阻,在没有getter和setter下轻而易举就能达到目的。同样,如果使用BetterReflection会更方便:
use Roave\BetterReflection\Reflection\ReflectionClass; $classInfo = ReflectionClass::createFromName(Student::class); $secretProperty = $classInfo->getProperty('secret'); echo $secretProperty->getValue($student) . PHP_EOL; // I love Fiona. $secretProperty->setValue($student,'hahaha'); echo $secretProperty->getValue($student) . PHP_EOL; // hahaha
关于如何在PHP中使用Reflection,可以看我之前写的文章,简单学习PHP中的反射。
Reflection是如此的强大,在没有权限的情况下,访问你的任何私有属性,可见在PHP中,属性修饰符只是给守规矩的人用的,不守规矩的人可以视而不见。
使用Closure
其实,如果仔细研究,就会发现能跳过访问修饰符的不止有Reflection,还有Closure,就是我们常说的闭包,再通俗点就是没有名字的函数,这是PHP 5.3加入的特性,也很简单,就提供了如下几个方法:
final class Closure { private function __construct() { } public function __invoke(...$_) { } function bindTo($newthis, $newscope = 'static') { } static function bind(Closure $closure, $newthis, $newscope = 'static') { } function call ($newThis, ...$parameters) {} public static function fromCallable (callable $callable) {} }
现在,我们先来看看怎么用Closure获取private:
class Student { public $name = 'Daniel'; public $age = 18; private $secret = '我要炸学校'; private static $foo = 'Hello'; } $accessor1 = function () { return $this->secret; }; $accessor2 = function () { return Student::$foo; }; $bind_accessor1 = Closure::bind($accessor1, new Student(), Student::class); $bind_accessor2 = Closure::bind($accessor2, null, Student::class); var_dump($bind_accessor1()); // 我要炸学校 var_dump($bind_accessor2()); // Hello
打印结果的确是我们想要的,对于私有静态成员变量这里要注意下,因为静态是属于类的,所以要这么写return Student::$foo,而且在bind的第二个参数不能写指定的对象。
获取没有问题,那我们想在尝试修改一下私有成员变量的值吧,代码稍作修改:
$accessor1 = function () { $this->secret = '你去炸学校,我就去告诉老师。'; return $this->secret; }; $accessor2 = function () { Student::$foo = 'hello hacker'; return Student::$foo; }; // output string(42) "你去炸学校,我就去告诉老师。" string(12) "hello hacker"
也完全可以修改。
现在看来,PHP中的访问修饰符只不过是一个摆设,但是在没有学Reflection之前我并不会这么觉得,可见多学一点旁门左道(有点用词不当)有助于拓宽思路,第二种是通过Closure的方式修改,就是通过一个匿名函数把当前的this指向到一个对象,就像一把手术刀划开一个很小的口子,能窥探到内部。