另一方面,如果能够深入理解PHP的邪术方法,并加以灵巧、恰当地利用,你将能节省很多重复性的代码编写,具备在陌生环境更顽强的代码生存能力,还能对某些看似神奇的征象做出合理的阐明。

下面,我们将来一起踏上这片邪术之地。

4.3.1 连续磋商DI容器背后的技巧

前面有说到Phalcon和PhalApi这两个PHP开源框架的DI容器,也见识了它的数组访问形式。
但它的利用办法不止这一种,还有两种是和本次要谈论的邪术方法有关。
我们先来看终极客户真个利用效果,再反过来追寻它背后的实现和事理。

phpgetterPHP高等编程回归原生态数组类和魔术办法 RESTful API

以PhalApi框架为例,对付做事资源的注册和获取,还可以通过类属性以及类成员函数来操作。
例如:

// 通过类属性办法操作$di->request = new \PhalApi\Request();var_dump($di->request);// 通过类成员函数办法操作$di->setRequest(new \PhalApi\Request());var_dump($di->getRequest());

这样是不是很酷?!
开拓工程师完备可以根据自己的喜好来选择操作办法,不用再担心会忘却如何利用DI容器。
那么这些炫酷的殊效是如何实现的呢?

如果查看PhalApi框架中DependenceInjection类的源代码,是找不到上面这些类属性和类成员函数的。
事实上,它也不可能穷举全部开拓职员会用到哪些资源做事。
为此,只能利用动态的办法来掩护。
如果细心品读DependenceInjection类的源代码,我们可以找到邪术方法的影子,顺着这些蛛丝马迹,我们就能领略邪术方法的美妙之处。

在给不可访问属性赋值时,__set() 会被调用。
读取不可访问属性的值时,__get() 会被调用。
以是,当对$di->request进行赋值时,会触发DependenceInjection内的__set()方法,对应代码是:

public function __set($name, $value) { $this->set($name, $value); }

而当通过$di->request获取不存在的属性时,会触发DependenceInjection内的__get() 方法,对应代码是:

public function __get($name) { return $this->get($name, NULL); }

常日情形,__set() 和__get() 是配套利用的。

再来看下其余一个邪术方法——__call(),当在工具中调用一个不可访问的方法时,就会触发这个邪术方法。
例如,实行$di->setRequest()操作时,就会触发DependenceInjection内的__call()方法,对应代码是:

public function __call($name, $arguments) { if (substr($name, 0, 3) == 'set') { $key = lcfirst(substr($name, 3)); return $this->set($key, isset($arguments[0]) ? $arguments[0] : NULL); } else if (substr($name, 0, 3) == 'get') { $key = lcfirst(substr($name, 3)); return $this->get($key, isset($arguments[0]) ? $arguments[0] : NULL); } throw new InternalServerErrorException( T('Call to undefined method DependenceInjection::{name}() .', array('name' => $name)) ); }

轻微阐明一下,__call()方法的第一个参数是要调用的方法名称,第二个参数是数组类型,即通报过来的参数列表。
在这里,先判断调用的方法因此set还是以get开头,然后如果有通报参数再将参数列表通报下去。
末了如果既不是set也不get操作,则抛出非常,奉告开拓职员存在造孽调用。

4.3.2 邪术方法与代码天生

顺便说一下,邪术方法都因此双下划线开头的。
此外,引申两点。
先说简短的,再说稍长的。
第一点, 当调用工具中一个不存在的方法时,会触发__call()邪术方法,那如果考试测验调用的是类的静态方法,又会触发哪个魔术方法呢?答案是:__callStatic()。
它的参数以及功能,和__call()类似,唯一不同点是名称以及须要利用static关键字,它的函数署名是:

publicstaticmixed__callStatic(string$name,array$arguments)

有兴趣的同学可以自行实现一个详细的示例,并考试测验对它进行利用。

第二点是,有人担心过多调用邪术方法会影响性能,因此会禁用邪术方法。
但我以为,既然选择了PHP这门措辞,就不会过多关注相差几毫秒的性能。
事实上,大型系统的性能瓶颈都不在于措辞的实行层面,而紧张集中于I/O方面,例如文件I/O,网络I/O,数据库I/O。
但这也给了我们另一个启示,如果确实须要关注性能,我们也可以对付常见的setter/getter提前天生相应的PHP代码。
例如针对数据传输工具DTO,就可以利用这一招。

先来看下,利用魔术方法的实现办法。
很大略,起一个得当的类名,然后重载__call()这个方法即可,非常大略。

<?phpclass DTO { public function __call($method, $params) { if (substr($method, 0, 3) == 'set') { $key = lcfirst(substr($method, 3)); $this->$key = $params[0]; } else if (substr($method, 0, 3) == 'get') { $key = lcfirst(substr($method, 3)); return isset($this->$key) ? $this->$key : NULL; } }}

出于大略性,这里暂时不对非常的情形作过多的预防和处理。
同样,客户端利用setter/getter也是非常大略的。
例如这样:

$dto = new DTO();$dto->setName('dogstar');var_dump($dto->getName());

这些都是没什么难度的,一旦你熟习邪术方法后。
如果在大型企业系统中,想得到更多细致的掌握权,也可以为此提前自动天生setter/getter的代码。
编写一个代码天生器,对付初学者来说会有点难度,乃至对付从没打仗过这块的同学来说也会有点陌生。
但一旦在实际项目中运用过后,你就会创造实在代码自动天生也是很大略的,而且运用处景很多。
这里以自动天生setter/getter代码为例,先大略说一下实现的思路,再来先容代码天生在各大开源框架中的运用处景。

每个DTO的类代码,类名是不一样的,其余各自的类属性也是不尽相同的。
如果我们好手动编写个中一个DTO的类代码,就能知道其它DTO的类代码要如何天生了。
快速来写一个代码天生器脚本 ,命名为:generate_dto_class.php,并在内放置以下实当代码:

<?php// DTO大略单纯代码天生器$class = $argv[1];$properties = array_slice($argv, 2);$code = "<?phpclass $class {";foreach ($properties as $it) { $itUpper = ucfirst($it); $code .= " public function set{$itUpper}(\${$it}) { \$this->{$it} = \${$it}; } public function get{$itUpper}() { return \$this->{$it}; }";}$code .= "}";file_put_contents(dirname(__FILE__) . '/' . $class . '.php', $code);echo "OK!\n";

开拓完成后,实行以下命令:

$ php ./generate_dto_class.php Student name age

就可以天生一个Student的DTO类,里面有两个类成员属性,分别是name和age。
并且,可以看到在天生的Student.php文件里有以下自动天生的PHP代码:

<?phpclass Student { public function setName($name) { $this->name = $name; } public function getName() { return $this->name; } public function setAge($age) { $this->age = $age; } public function getAge() { return $this->age; }}

是不是很有趣?

在代码天生这一领域,不同的开源框架有不同的做法。
Yii框架供应了Gii,一个强大的基于Web 的代码天生器,可以天生Model类的代码,以及CRUD代码。
Symfony框架则可以利用Doctrine组件供应的命令来创建Entity实体类的代码。
例如输入以下命令并按提示操作:

$ php bin/console make:entityClass name of the entity to create or update:> Product

末了可以天生类似这样的代码:

// src/Entity/Product.phpnamespace App\Entity;use Doctrine\ORM\Mapping as ORM;/ @ORM\Entity(repositoryClass="App\Repository\ProductRepository") /class Product{ / @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") / private $id; public function getId(){ return $this->id; } // ... getter and setter methods}

代码自动天生更多是运用在与数据库操作干系的层级上,例如DTO、实体Entity、模型Model。
在我曾经任职的第一家公司里,也供应了一个强大的命令,可以根据xml的配置,自动天生相应的整套数据库干系操作的代码库。
另一方面,在其他场景也可以创造代码天生的身影。
例如,在PhalApi框架中,供应了phalapi-buildtest命令,可自动天生测试代码。

如果想提升自己的开拓效率,提升全体项目的交付速率,魔术方法或者代码天生,都是值得推举的策略。
前者可以节省编写重复的代码,后者则可以直接帮你天生重复的代码。
何乐而不为?