🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 增强PHP语言 Nette Framework使用一些特殊语法candy扩展了PHP的对象模型。 你喜欢candy吗? 继续阅读,你会发现更多好处。 1、为什么你应该使用Nette \ Object 2、什么是属性 3、如何调用事件 4、如何向类添加方法 5、如何使用@annotations 在本章中,我们关注Nette \ Object,一个扩展PHP对象模型的类。 它是Nette Framework中几乎所有类的祖先。 作为透明和不冲突的,它可以作为每个类的祖先。 ## 严格的类 PHP给开发人员一个巨大的自由空间,这使得它语言很易犯错误。 但是你可以阻止这种不良行为,从开始编写应用程序,而不会发现错误。 你想知道怎么样做? 这很简单 - 你只需要有更严格的规则。 你能在这个例子中找到一个错误吗? ~~~ class Circle { public $radius; public function getArea() { return $this->radius * $this->radius * M_PI; } } $circle = new Circle; $circle->raduis = 10; echo $circle->getArea(); // 10² * π ≈ 314 ~~~ 首先看看代码将打印出来314; 但它返回0。这怎么可能? 意外,$ circle-> radius被错误地输入raduis。 只是一个小的错字,这将给你一个很难纠正它,因为PHP不会说出那里不对。 甚至不是警告或通知的错误消息。 因为PHP不认为这是一个错误。 上述错误可以立即纠正,如果Circle类将是Nette \ Object的后代: ~~~ class Circle extends Nette\Object { ... ~~~ 虽然前面的代码执行成功(虽然它包含一个错误),后者没有: ![](https://box.kancloud.cn/944d1d93be63c0339bf9b16154db6a5b_490x360.png) Nette \ Object类制作 Circle更严格,并且当您尝试访问未声明的属性时抛出异常。 Tracy显示了关于它的错误信息。 具有致命错字的代码行现在突出显示,并且错误消息具有有意义的描述:无法写入未声明的属性Circle :: $ raduis。 程序员现在可以解决他可能会错过的错误,这可能是一个真正的痛苦,以后找到。 Nette \ Object的许多显着的能力之一是在访问未声明的成员时抛出异常。 ~~~ $circle = new Circle; echo $circle->undeclared; // throws Nette\MemberAccessException $circle->undeclared = 1; // throws Nette\MemberAccessException $circle->unknownMethod(); // throws Nette\MemberAccessException ~~~ 但它有更多的提供! ## Properties, getters and setters 在现代面向对象语言中,属性描述类的成员,它们看起来像变量,但是由方法表示。 当读取或赋值给这些“变量”时,会调用方法(所谓的getter和setter)。 这是非常有用的功能,它允许我们控制对这些变量的访问。 使用它,我们可以验证输入或推迟这些变量的值的计算到实际访问的时间。 任何作为Nette \ Object的后代的类都获得了模仿属性的能力。 只有你需要做的是保持简单的约定: * Getter和setter必须是公共方法。 * getter的名字是getXyz()或isXyz(),setter的是setXyz() * Getter和setter是可选的,因此可以具有只读或只写属性 * 属性名称区分大小写(第一个字母为异常) 我们将使用Circle类中的属性来确保变量$ radius只包含非负数: ~~~ class Circle extends Nette\Object { private $radius; // not public anymore! public function getRadius() { return $this->radius; } public function setRadius($radius) { // sanitizing value before saving it $this->radius = max(0.0, (float) $radius); } public function getArea() { return $this->radius * $this->radius * M_PI; } public function isVisible() { return $this->radius > 0; } } $circle = new Circle; // the classic way using method calls $circle->setRadius(10); // sets circle's radius echo $circle->getArea(); // gets circle's area // the alternative way using properties $circle->radius = 10; // calls setRadius() echo $circle->area; // calls getArea() echo $circle->visible; // calls $circle->isVisible() ~~~ 属性主要是一个语法candy,美化代码,使程序员的生活更轻松。 你不必使用它们,如果你不想。 ## 事件 现在我们要创建函数,当边界半径改变时调用。 让我们调用它change事件和那些函数事件处理程序: ~~~ class Circle extends Nette\Object { /** @var array */ public $onChange; public function setRadius($radius) { // call events in onChange $this->onChange($this, $this->radius, $radius); $this->radius = max(0.0, (float) $radius); } } $circle = new Circle; // adding an event handler $circle->onChange[] = function($circle, $oldValue, $newValue) { echo 'there was a change!'; }; $circle->setRadius(10); ~~~ setRadius的代码中还有一个语法candy。 而不是在$ onChange数组上的迭代,并调用每个方法一个接一个与不可靠(不报告如果回调有任何错误)function call_user_func,你只需要写简单的onChange(...)和给定的参数将被移交给 处理程序。 ## 扩展方法 你需要在运行时向现有对象或类添加一个新方法吗? 扩展方法就是你所需要的。 ~~~ // declaration of future method Circle::getCircumference() Circle::extensionMethod('getCircumference', function (Circle $that) { return $that->radius * 2 * M_PI; }); $circle = new Circle; $circle->radius = 10; echo $circle->getCircumference(); // ≈ 62.8 ~~~ 扩展方法也可以采取参数。 它们不会打破封装,因为它们只能访问类的公共成员。 您还可以将它们与接口连接,因此实现该接口的每个类都将具有该方法。 ## 反射 如果你需要找到关于任何类的每一个信息,反射是正确的工具。 你可以很容易地找出任何类有哪些方法,这些方法接受什么参数等等。Nette \ Object使用getReflection()方法简化类的自我反射的访问,返回一个ClassType对象: ~~~ $circle = new Circle; echo $circle->getReflection()->hasMethod('getArea'); // does method 'test' exist? echo $circle->getReflection()->getName(); // returns the name of the class ('Circle') ~~~ 即使在这种情况下,我们可以使用属性约定,并将最后一行更改为: ~~~ echo $circle->reflection->name; // returns 'Circle' ~~~ 可以获得反射而不使用Nette \ Object: ~~~ // getting PDO class reflection $classReflection = new Nette\Reflection\ClassType('PDO'); // getting PDO::query method reflection $methodReflection = new Nette\Reflection\Method('PDO', 'query'); ~~~ ## 注释 反射与注释有很多关系。 注释写入phpDoc注释(两个开头的星号是必须的!),以@开头。 您可以注释类,变量和方法: ~~~ /** * @author John Doe * @author Tomas Marny * @secured */ class FooClass { /** @Persistent */ public $foo; /** @User(loggedIn, role=Admin) */ public function bar() {} } ~~~ 在代码中有这些注释: * @author John Doe – string, contains text value 'John Doe' * @Persistent – boolean, its presence means TRUE * @User(loggedIn, role=Admin) – contains associative ['loggedIn', 'role' => 'Admin'] 类注解的存在可以通过hasAnnotation()方法检查: ~~~ $fooReflection = new Nette\Reflection\ClassType('FooClass'); $fooReflection->hasAnnotation('author'); // returns TRUE $fooReflection->hasAnnotation('copyright'); // returns FALSE ~~~ 可以使用getAnnotation()获取值: ~~~ $fooReflection->getAnnotation('author'); // returns string 'Tomas Marny' $fooReflection->getMethod('bar')->getAnnotation('User'); // returns ['loggedIn', 'role' => 'Admin'] ~~~ 所有注释都可以通过getAnnotations()获取: ~~~ array(3) { "author" => array(2) { 0 => string(8) "John Doe" 1 => string(11) "Tomas Marny" } "secured" => array(1) { 0 => bool(TRUE) } } ~~~