dev-master
9999999-devLibrary to tokenize, edit and override PHP classes
MIT
Library to tokenize, edit and override PHP classes
Library to tokenize, edit and override PHP classes., (*1)
It can be installed with composer in the CLI. First add the package to your composer.json, (*2)
"require": { ... "docdigital/php-class-editor": "dev-master" },
then update your project, (*3)
$ php composer.phar update docdigital/php-class-editor
Or just:, (*4)
$ php composer.phar require docdigital/php-class-editor:dev-master
And including the classes in your code:, (*5)
use DocDigital\Lib\SourceEditor\PhpClassEditor; use DocDigital\Lib\SourceEditor\ClassStructure\ClassElement;
You might also need to include composer's autoload, (*6)
Testing and suggestions are very wellcome., (*7)
Attempts to handle modiffications in existing PHP Class, by adding annotations to specific properties, adding properties and methods., (*8)
This is useful for instance to add annotations to an entity Generated by {@link \Doctrine\ORM\Tools\EntityGenerator}, (*9)
The Idea is to make is simple to get parts of the code as relevant elements and
manipulate them. In the next example I have 3 relevant elements, each having
sub-elements accessible from the main (parent??) one., (*10)
/** * Class Doc Block */ class a { /** * Attr Doc Block */ public $attr /** * Fn Doc Block */ public function b() { } }
```XML
Then you should be able to issue: ```php /* @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;');
When finished you must render the file., (*12)
// 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));
to get a reference to the Class representation (ClassElement) you have to do something like this, (*13)
/** * 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; }
In this example Method I add Constants to a Doctrine Entity programatically, to specify ROLES the user must have, (*14)
/** * 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()))); }
the result afer overriding the file is:, (*15)
/** * 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'; ...
Here's TokenParser.php's DocBlock. The parser is a decoupled piece of code, PhpClassEditor relies on it and gives it 2 Closures to arrange the class composite structure., (*16)
php
/**
* 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>
*/
, (*17)
Library to tokenize, edit and override PHP classes
MIT