docdigital/php-class-editor

用于标记、编辑和覆盖 PHP 类的库

dev-master 2014-08-08 02:08 UTC

This package is not auto-updated.

Last update: 2024-09-28 15:29:01 UTC


README

用于标记、编辑和覆盖 PHP 类的库。

安装

可以使用 CLI 中的 composer 安装。首先将包添加到您的 composer.json 文件中

    "require": {
       ...
        "docdigital/php-class-editor": "dev-master"
    },

然后更新您的项目

$ php composer.phar update docdigital/php-class-editor

或者直接

$ php composer.phar require docdigital/php-class-editor:dev-master

并在您的代码中包含这些类

use DocDigital\Lib\SourceEditor\PhpClassEditor;
use DocDigital\Lib\SourceEditor\ClassStructure\ClassElement;

您可能还需要包含 composer 的 自动加载

欢迎提出测试和建议。

概述

通过添加注释到特定属性、添加属性和方法来尝试处理现有 PHP 类的修改。

例如,这有助于为通过 {@link \Doctrine\ORM\Tools\EntityGenerator} 生成的实体添加注释。

想法是使代码的部分作为相关元素获取和处理变得简单。在下一个示例中,我有 3 个相关元素,每个元素都有
可以从主要元素(父元素)访问的子元素。

 /**
  * Class Doc Block
  */
 class a
 {
     /**
      * Attr Doc Block
      */
     public $attr
     
     /**
      * Fn Doc Block
      */
     public function b()
     {
   }
 }
 <elemnet class>
     <docBlock/>
     <element attribute>
         <docBlock/>
     </element >
     <element method>
         <docBlock/>
     </element >
 </elemnet> 

然后您可以执行以下操作

    /* @var $classEditor DocDigital\Lib\SourceEditor\PhpClassEditor */
    $classEditor->parseFile($classPath);
    $classEditor->getClass('a')->getMethod('b')->addAnnotation('@auth Juan Manuel Fernandez <juanmf@gmail.com>');
    $classEditor->getClass('a')->getAttribute('attr')->addAnnotation('@Assert\Choice(...)');
    $classEditor->getClass('a')->addAttribute($attr2);
    $classEditor->getClass('a')->addUse('use DocDigital\Bundle\DocumentBundle\DocumentGenerator\Annotation as DdMapping;');
    $classEditor->getClass('a')->addConst('    const CONSTANT = 1;');

完成时,您必须渲染文件。

    // Element::render() is a composite pattern implementation, class renders its children and so on. 
    file_put_contents('PATH/TO/CLASS/FILE', $classEditor->getClass('a')->render(false));

要获取类表示(ClassElement)的引用,您必须执行如下操作

    /**
     * Used to add annotations and validaton methods on generated php classes
     * 
     * @var PhpClassEditor 
     */
    private $phpClassEditor;
    
    /**
     * Parses The just writteng PHP Entity and returns a CalssElement representation.
     * 
     * @param \DocDigital\Bundle\DocumentBundle\Entity\DocumentType $docDefinition
     * 
     * @return DocDigital\Lib\SourceEditor\ClassStructure\ClassElement A class 
     * definition representing the Document being Modiffied.
     */
    public function parseDocument(DocumentType $docDefinition)
    {
        $classPath = $this->getDocumentClassPath($docDefinition);
        $classes = $this->phpClassEditor->parseFile($classPath);
        $document = end($classes);
        return $document;
    }

在这个示例方法中,我通过编程将常量添加到 Doctrine 实体中,以指定用户必须拥有的 ROLES

    /**
     * Adds standar ROLES to this entity, as constants useful to ask consistently
     * for a given ROLE in any Controller or view.
     * 
     * @param ClassElement $docDefinition
     * @param DocumentType $docDefinition
     */
    public function addEntityRoles(ClassElement $document, DocumentType $docDefinition)
    {
        $docBlock = "/**\n    * Role required for this Document to perform this action\n    */";
        $document->addConst(
                sprintf('    const ROLE_LIST = \'ROLE_%s_LIST\';', strtoupper($docDefinition->getClassName())),
                $docBlock
            );
        $document->addConst(sprintf('    const ROLE_VIEW = \'ROLE_%s_VIEW\';', strtoupper($docDefinition->getClassName())));
        $document->addConst(sprintf('    const ROLE_EDIT = \'ROLE_%s_EDIT\';', strtoupper($docDefinition->getClassName())));
        $document->addConst(sprintf('    const ROLE_CREATE = \'ROLE_%s_CREATE\';', strtoupper($docDefinition->getClassName())));
        $document->addConst(sprintf('    const ROLE_DELETE = \'ROLE_%s_DELETE\';', strtoupper($docDefinition->getClassName())));
    }

覆盖文件后的结果是

/**
 * Asd
 *
 * @ORM\Table(name="custom_doc_asd")
 * @ORM\Entity
 */
class Asd extends \DocDigital\Bundle\DocumentBundle\Entity\Document
{

    /**
    * Role required for this Document to perform this action
    */
    const ROLE_LIST = 'ROLE_ASD_LIST';

    /**
     *
     */
    const ROLE_VIEW = 'ROLE_ASD_VIEW';

    /**
     *
     */
    const ROLE_EDIT = 'ROLE_ASD_EDIT';

    /**
     *
     */
    const ROLE_CREATE = 'ROLE_ASD_CREATE';

    /**
     *
     */
    const ROLE_DELETE = 'ROLE_ASD_DELETE';
    ...

内部

这是 TokenParser.php 的 DocBlock。解析器是一段独立的代码,PhpClassEditor 依赖于它,并提供了 2 个闭包来安排类复合结构。

/**
 * Handles Token classification, code {@link ElementBuilder} creation and code context/scope
 * changes detection. Each ElementBuilder will contain either a significant code part or gap code,
 * like T_WHITESPACE or unclassified code, like method body (as currently not inspecting inside method).
 * 
 * Every time an ElementBuilder is closed, or a context changes (which also closes an ElementBuilder)
 * it gets forwarded to a couple of callback closures given by client code:<pre>
 *    {@link self::$contextChangeClosure}
 *    {@link self::$processElementClosure}
 *</pre>
 * Which are passed as parameters to {@link self::setSource()}
 * 
 * The basic sequence is:<pre>
 * +------------+  +--------------+
 * | :ClientObj |  | :TokenParser |
 * +------------+  +--------------+
 *      |              |
 *      |--setSource-->|
 *      |--parseCode-->|--readToken--+[forEach Token, this iteration changes context as token is a contextStart]
 *      |              ||<-----------+
 *      |              ||--read<CONTEXT>Token--+
 *      |              |||<--------------------+
 *      |              |||--_checkContextChange--+
 *      |              ||||<---------------------+
 *      |              ||||--_isContextStart--+
 *      |              |||||<-----------------+
 *      |              ||||--_changeContext--+ [if Context changed e.g. class=>method]
 *      |              |||||<----------------+
 *      |              |||||--_startNewElement--+
 *      |              ||||||<------------------+
 *      |              ||||||------------------------------+
 *      ||<--$processElementClosure(self::elementBuilder)--+
 *      |              |||||
 *      |              |||||------------------------------------------------------------+
 *      ||<--$contextChangeClosure($newContext, $currentContext, self::elementBuilder)--+
 *      |              |||||
 *      |              |||||--readToken--+ [reReads Token without increasing {@link self::pointer}]
 *      |              ||||||<-----------+
 *      |              |
 *      |              |[continue Looping forEach Token at parseCode, now reads a token that doesn't change context]
 *      |              |
 *      |              |--readToken--+[forEach Token]
 *      |              ||<-----------+
 *      |              ||--read<CONTEXT>Token--+
 *      |              |||<--------------------+
 *      |              |||--_checkContextChange--+
 *      |              ||||<---------------------+
 *      |              ||||--_isContextStart--+
 *      |              |||||<-----------------+
 *      |              ||||--_isContextEnd--+ [Called only if _isContextStart returns false, also 
 *      |              |||||<---------------+      returns false for this token]
 *      |              |||
 *      |              |||--_loadTokenInElement--+ [context didn't change, add token to ElementBuilder]
 *      |              ||||<---------------------+
 *      |              ||||--_startNewElement--+   [Only if token is a delimiter Flag that closes current 
 *      |              |||||<------------------+       self::elementBuilder again calling $processElementClosure]
 *      |              |
 *      |              |[continue Looping forEach Token at parseCode, now reads a token that doesn't change context]
 *      |              |
 * 
 * @author Juan Manuel Fernandez <juanmf@gmail.com>
 */