另一方面,如果能够深入理解PHP的邪术方法,并加以灵巧、恰当地利用,你将能节省很多重复性的代码编写,具备在陌生环境更顽强的代码生存能力,还能对某些看似神奇的征象做出合理的阐明。
下面,我们将来一起踏上这片邪术之地。
4.3.1 连续磋商DI容器背后的技巧
前面有说到Phalcon和PhalApi这两个PHP开源框架的DI容器,也见识了它的数组访问形式。但它的利用办法不止这一种,还有两种是和本次要谈论的邪术方法有关。我们先来看终极客户真个利用效果,再反过来追寻它背后的实现和事理。
以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命令,可自动天生测试代码。
如果想提升自己的开拓效率,提升全体项目的交付速率,魔术方法或者代码天生,都是值得推举的策略。前者可以节省编写重复的代码,后者则可以直接帮你天生重复的代码。何乐而不为?