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> */