开放的编程资料库

当前位置:我爱分享网 > PHP教程 > 正文

使用 Zend\CodeGenerator 生成代码

ZendFramework从1.8版开始提供代码生成组件,当时我们开始发布Zend_ToolZend_CodeGenerator很大程度上模仿了PHP的反射API,但恰恰相反:它生成代码。

为什么要生成代码?

  • 您可以将它用作常见任务的“复制和粘贴”辅助形式(例如,它在zf.sh中用于生成控制器类和操作方法)。
  • 您可能想要从配置生成代码,以移除从配置值生成对象的“编译”阶段。通常这样做是为了在严重依赖可配置值的情况下提高性能。

ZF2存储库中的

Zend\CodeGenerator主要是从ZendFramework1移植过来的,但也包括一些围绕命名空间使用和导入的功能。我这周在处理一些原型时使用了它,发现它很有用够了,我想分享一些我学到的东西。

基础知识

在大多数情况下,您需要查看API方法以了解您可以创建什么。各种类都在Zend\CodeGenerator\Php命名空间中(子命名空间是为了让我们在未来的某个时候可以包含PHP以外的格式和语言的代码生成),它们包括:

  • Docblock\Tag\LicenseTag(为文档块生成“许可证”注释)
  • Docblock\Tag\ParamTag(生成“参数””docblocks注释)
  • Docblock\Tag\ReturnTag(为docblocks生成“return”注释)
  • PhpBody(生成任意PHP内容;通常用于填充文件或方法调用)
  • PhpClass(生成PHP类)
  • PhpDocblock(生成PHPdocblocks)
  • PhpDocblockTag(生成任意的dockblock注释)
  • PhpFile(生成PHP文件)
  • PhpMethod(生成PHP类方法)
  • PhpParameterDefaultValue(为PHP方法/函数参数生成默认参数值)
  • PhpParameter(生成PHP方法/函数参数)
  • PhpProperty(生成PHP类属性)
  • PhpPropertyValue(生成PHP属性值arg文件;即实例化时的默认属性值)
  • PhpValue(生成任意PHP值赋值语句)

在大多数情况下,您可以调用setContent()和/或setName()方法;其他方法将根据上下文提供。所有类还包含一个generate()方法,该方法将根据对象的当前状态生成代码。

这些类中的大多数在孤立情况下没有多大用处,而是与其他对象交互以创建预期代码。

例如,我构建的原型正在生成一个PHP类文件。要求包括:

  • 设置命名空间
  • 定义一个或多个类导入
  • 定义一个类,扩展另一个类
  • 为该类定义多个方法,带代码;至少在一种情况下,生成的方法还需要参数

这实际上相对容易;最难的部分是为各个方法生成实际的代码体!

例如,我们现在将生成一个类骨架:

use Zend\CodeGenerator\Php as CodeGen;
$file = new CodeGen\PhpFile();
$file->setNamespace('Application')
     ->setUses('Zend\Di\DependencyInjectionContainer', 'DIC');
     
$class = new CodeGen\PhpClass();
$class->setName('Context')
      ->setExtendedClass('DIC');

$get = new CodeGen\PhpMethod();
$get->setName('get')
    ->setParameters(array(
        new CodeGen\PhpParameter(array('name' => 'name')),
        new CodeGen\PhpParameter(array(
            'name' => 'params',
            'defaultValue' => new CodeGen\PhpParameterDefaultValue(array(
                'value' => array(),
            )),
        )),
    ));

$class->setMethod($get);

$file->setClass($class);

echo $file->generate();

以上将生成以下内容:

<?php

namespace Application;

use Zend\Di\DependencyInjectionContainer as DIC;

class Context extends DIC
{

    public function get($name, $params = array())
    {
    }


}

一些提示和陷阱:

  • 与大多数ZF一样,可以配置任何setter方法。键名对应于setter方法,减去“set”,首字母小写—因此,setName()可以通过传递配置键“name”来触发;setDefaultValue()和“defaultValue”。

  • 在大多数情况下,您不需要提供对象;您可以传递表示预期对象类型的配置值的数组。例如,将值数组作为项目传递给setParameter()会将配置传递给PhpParameter的构造函数。也就是说,我发现进行显式对象声明更可预测且更易于阅读。

  • 如果您的默认参数值是一个数组,则您必须跳过一些环节。通常,您可以简单地为setDefaultValue()方法(或“defaultValue”键)指定要使用的值,但数组被视为配置。因此,您需要在这些情况下显式创建一个PhpParameterDefaultValue(就像我在上面的示例中所做的那样)。

  • 在上面,我除了骨架之外没有生成任何东西。然而,在我的实际原型中,我正在为方法的主体内容生成代码。我发现sprintf是我的朋友,代表缩进量的变量或常量也是如此。例如:

    $caseStatements = array();
    foreach ($definitions as $definition) {
        // ...
    
        $caseStatement  = '';
        foreach ($cases as $case) {
            $caseStatement .= sprintf("%scase '%s':\n", $indent, $case);
        }
        $caseStatement .= sprintf("%sreturn \$this->%s();\n", str_repeat($indent, 2), $getter);
        $caseStatements[] = $caseStatement;
    }
    
    $switch = sprintf("switch (\$name) {\n%s}\n", implode($caseStatements, "\n"));
    
    $method->setBody($switch); // PhpMethod object
    

    这又生成了以下内容:

    switch ($name) {
        case 'foo':
        case 'My\Component\Foo':
            $this->getMyComponentFoo();
    
    }
    

为什么?

它可能看起来像很多代码,您可能想知道,“为什么要这么麻烦?”不过,要点在于它是可预测和可测试的——这使它有可能成为模板化解决方案。我基本上可以确保我想要的结构与使用DOM构造XML相似—如果需要,稍后可以更改它。

此外,在我的特定用例中——实际上,这是一个常见用例——我正在使用可预测的配置结构,并希望一遍又一遍地生成一些东西。当我的配置发生变化时,我希望能够更新代码,而不必担心我忘记了什么或引入了新的错字(除了我在配置文件中创建的错字)。关键是,这是我将一遍又一遍地编写的代码,因此拥有一个生成它的工具将节省我的时间。

此外,在这个特定的用例中,生成的代码比运行生成它的代码更快,因为它避免了最终生产阶段的“配置”步骤。通过生成代码,我可以绕过诸如反射之类的事情,使用更有效的做法(例如,使用call_user_func()或直接方法调用而不是call_user_func_array()),并引入类型提示在以前依赖字符串的区域。

完成

Zend\CodeGenerator中提供了大量的功能,而我在这篇文章中只触及了冰山一角。有哪些用于代码生成的用例?有哪些技巧可以分享?

未经允许不得转载:我爱分享网 » 使用 Zend\CodeGenerator 生成代码

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏