コード例 #1
0
 public function enterNode(Node $node)
 {
     if (!$node instanceof Class_) {
         return;
     }
     if ($this->classes->exists($node)) {
         throw new Exception("Class {$node->name} already encountered");
     }
     $properties = new SimpleOrderedMap();
     foreach ($node->stmts as $stmt) {
         if (!$stmt instanceof Property) {
             continue;
         }
         foreach ($stmt->props as $prop) {
             if (!in_array($prop->name, $this->properties, true)) {
                 continue;
             }
             if ($properties->exists($prop)) {
                 throw new Exception("Property {$prop->name} already exists");
             }
             if (!$prop->default instanceof Array_) {
                 continue;
             }
             $symbols = self::arrayExtractItems($prop->default);
             if (empty($symbols)) {
                 continue;
             }
             asort($symbols);
             $properties->add($prop, $symbols);
         }
     }
     $this->classes->add($node, $properties);
 }
コード例 #2
0
 /**
  * Rewrite the PHPDOC of a class to contain the documentation of the
  * special CakePHP2 magic properties.
  *
  * $classes maps parsed PHP classes to properties and each property contains
  * a mapping of injected property names to their class types
  *
  * This nodes/statement from PhpParser also contain additional information
  * about the PHPDOC and in which lines in the source those elements are.
  * This information is used to either create a new PHPDOC or change an
  * existing one to contain the magic properties.
  *
  * Note: it may seem redundant that the $classes->property->symbol mapping
  * already has all properties and a separate $properties list is required.
  * However during collecting a property we don't know yet which top level
  * class it is in, i.e. which properties apply to the current class.
  * This is solves here insofar the top level class check happened (outside)
  * already and thus we get a specific list of $properties to document.
  *
  * @param array $code The code of the PHP file as returned by the file()
  *                    command, i.e. an every with every line in the file
  *                    including EOL.
  * @param SimpleOrderedMap $classes Mapping of classes to the collected
  *                                  property which maps to a list of symbols.
  * @param string[] $properties Name the properties whose symbols should be
  *                              written to PHPDOC.
  * @param bool $removeUnknownProperties Removes all other properties (first)
  *                                      and then adds the found ones. This may
  *                                      remove properties which have been
  *                                      manually added so use with care.
  * @throws Exception
  * @throws SimpleOrderedMapException
  * @return array Returns the (possible) modified PHP source code.
  */
 public static function apply(array $code, SimpleOrderedMap $classes, array $properties, $removeUnknownProperties = false)
 {
     if (empty($code)) {
         return $code;
         # nothing to do
     }
     $insertions = [];
     # record at which line what kind of insertions will happen
     /** @var Class_ $class */
     foreach ($classes->keys() as $class) {
         $currentInsertions = 0;
         # keep count how many properties == lines we've added
         # Detect existing or create new PhpDoc and figure out indentation
         $phpDoc = self::getLastPhpDocComment($class);
         $phpDocNumLinesInSource = 0;
         $docIndent = '';
         if ($phpDoc instanceof Doc) {
             # get indentation from existing phpdoc
             $text = $phpDoc->getText();
             if (preg_match(self::RE_INDENT, $text, $m)) {
                 $docIndent = $m['indent'];
             }
             # If it's a single line comment, we've to break it up
             if (!preg_match('/\\R/', $text)) {
                 $textNoEndComment = preg_replace(';\\*/\\s*;', '', $text);
                 # If there was no change to the text, we couldn't remove the end of
                 # comment we expected to be there; that's rather unexpected
                 if ($text === $textNoEndComment) {
                     throw new Exception('Unable to remove end doc comment marker \'*/\' from single line comment');
                 }
                 # Since we've add a new line we need to know the files EOL
                 $eol = self::extractEol(reset($code));
                 $text = $textNoEndComment . $eol . $docIndent . ' */';
                 $phpDoc->setText($text);
                 $lines = self::splitStringIntoLines($text);
                 $phpDocNumLinesInSource = 1;
                 # hardcoded because we know
             } else {
                 $lines = self::splitStringIntoLines($text);
                 $phpDocNumLinesInSource = count($lines);
             }
             if ($removeUnknownProperties) {
                 # Remove every line already containing a @property statement
                 foreach ($lines as $i => $line) {
                     if (false !== strpos($line, '@property')) {
                         unset($lines[$i]);
                     }
                 }
                 $lines = array_values($lines);
                 # ensure no gaps in indices
                 $phpDoc->setText(join('', $lines));
             }
             assert(0 !== $phpDocNumLinesInSource);
         } else {
             # No PHPDOC found, create a new one
             $default = self::DEFAULT_PHPDOC;
             # indent default phpdoc by current class indentation
             $classLine = $code[$class->getAttribute('startLine') - 1];
             if (preg_match(self::RE_INDENT, $classLine, $m)) {
                 $docIndent = $m['indent'];
                 # now prepend the indent before each line
                 $lines = preg_split("/(\\R)/", $default, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
                 for ($lineNr = 0; $lineNr < count($lines) >> 1; $lineNr++) {
                     # accommodate for extra EOL matches
                     $lines[$lineNr << 1] = $docIndent . $lines[$lineNr << 1];
                 }
                 $default = join('', $lines);
                 unset($lines);
             }
             $phpDoc = new Doc($default, $class->getAttribute('startLine'));
             unset($default);
         }
         $docIndent .= ' ';
         # extra space for slash
         # Go through the parsed properties and add them to the phpdoc if they
         # can't be found
         $currentProperties = $classes->get($class);
         /** @var Property $property */
         foreach ($currentProperties->keys() as $property) {
             if (!in_array($property->name, array_keys($properties))) {
                 continue;
             }
             $symbols = $currentProperties->get($property);
             foreach ($symbols as $symbol => $type) {
                 # Extract only class name part (i.e. throw away name of plugins)
                 $symbol = preg_replace('/.*\\.([^\\.]+)$/', '$1', $symbol);
                 $type = preg_replace('/.*\\.([^\\.]+)$/', '$1', $type);
                 $type = $properties[$property->name]($type);
                 if (self::addPropertyIfNotExists($phpDoc, $type, $symbol, $docIndent)) {
                     $currentInsertions++;
                 }
             }
         }
         if ($currentInsertions === 0) {
             # no lines added, nothing to do
             continue;
         }
         # Accumulate sum of previously replaced lines to get actual line number
         $alreadyAddedLines = array_reduce($insertions, function ($carry, $item) {
             return $carry + $item['replaceNumLines'];
         }, 0);
         $insertions[$phpDoc->getLine() - 1 + $alreadyAddedLines] = ['doc' => $phpDoc, 'replaceNumLines' => $phpDocNumLinesInSource];
     }
     foreach ($insertions as $lineNr => $data) {
         /** @var Doc $phpDoc */
         $phpDoc = $data['doc'];
         $text = $phpDoc->getText();
         $lines = self::splitStringIntoLines($text);
         array_splice($code, $lineNr, $data['replaceNumLines'], $lines);
     }
     return $code;
 }