Skip to content

Instantly share code, notes, and snippets.

@itsjavi
Last active August 29, 2015 14:20
Show Gist options
  • Select an option

  • Save itsjavi/be11193498473af7a846 to your computer and use it in GitHub Desktop.

Select an option

Save itsjavi/be11193498473af7a846 to your computer and use it in GitHub Desktop.
PHP Annotation Parser based on Nette AnnotationParser.php
<?php
/**
* Annotations support for PHP.
* Class based on the Nette Framework annotation parser.
*/
class AnnotationParser
{
/** @internal single & double quoted PHP string */
const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';
/** @internal identifier */
const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF-\\\]*';
/** @var array */
protected static $inherited = ['description', 'param', 'return'];
/**
* @var array Annotation namespaces
*/
protected $namespaces = [];
/**
* @param array $namespaces Annotation namespaces
*/
function __construct(array $namespaces = [])
{
$this->namespaces = array_merge($namespaces, [__NAMESPACE__]);
}
/**
* Returns annotations.
*
* @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $r The reflector object
*
* @return BaseAnnotation[]
*/
public function getAnnotations(\Reflector $r)
{
if ($r instanceof \ReflectionClass) {
$type = $r->getName();
$member = 'class';
//$file = $r->getFileName();
} elseif ($r instanceof \ReflectionMethod) {
$type = $r->getDeclaringClass()->getName();
$member = $r->getName();
//$file = $r->getFileName();
} elseif ($r instanceof \ReflectionFunction) {
$type = null;
$member = $r->getName();
//$file = $r->getFileName();
} else {
$type = $r->getDeclaringClass()->getName();
$member = '$' . $r->getName();
//$file = $r->getDeclaringClass()->getFileName();
}
$annotations = $this->parseComment($r->getDocComment());
// inheritdoc
if ($r instanceof \ReflectionMethod && !$r->isPrivate()
&& (!$r->isConstructor() || !empty($annotations['inheritdoc'][0]))
) {
try {
$inherited = $this->getAnnotations(new \ReflectionMethod(get_parent_class($type), $member));
} catch (\ReflectionException $e) {
try {
$inherited = $this->getAnnotations($r->getPrototype());
} catch (\ReflectionException $e) {
$inherited = [];
}
}
$annotations += array_intersect_key($inherited, array_flip(self::$inherited));
}
$annotationsInstances = [];
foreach ($annotations as $annName => $annValues) {
foreach ($annValues as $annValue) {
$annFullClassName = null;
$annBaseClassName = ucfirst($annName) . 'Annotation';
foreach ($this->namespaces as $ns) {
$ns = rtrim($ns, '\\') . '\\';
if (class_exists($ns . $annBaseClassName)) {
$annFullClassName = $ns . $annBaseClassName;
$annotationsInstances[] = new $annFullClassName($annValue);
break;
}
}
if (is_null($annFullClassName)) {
$annotationsInstances[] = new BaseAnnotation($annValue);
}
}
}
return $annotationsInstances;
}
/**
* Parses phpDoc comment.
*
* @param string
*
* @return array
*/
protected function parseComment($comment)
{
static $tokens = ['true' => true, 'false' => false, 'null' => null, '' => true];
$res = [];
$comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
$parts = preg_split('#^\s*(?=@' . self::RE_IDENTIFIER . ')#m', $comment, 2);
$description = trim($parts[0]);
if ($description !== '') {
$res['description'] = [$description];
}
$matches = $this->matchAll(
isset($parts[1]) ? $parts[1] : '',
'~
(?<=\s|^)@(' . self::RE_IDENTIFIER . ')[ \t]* ## annotation
(
\((?>' . self::RE_STRING . '|[^\'")@]+)+\)| ## (value)
[^(@\r\n][^@\r\n]*|) ## value
~xi'
);
foreach ($matches as $match) {
list(, $name, $value) = $match;
if (substr($value, 0, 1) === '(') {
$items = [];
$key = '';
$val = true;
$value[0] = ',';
while ($m = $this->match(
$value,
'#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
) {
$value = substr($value, strlen($m[0]));
list(, $key, $val) = $m;
$val = rtrim($val);
if ($val[0] === "'" || $val[0] === '"') {
$val = substr($val, 1, -1);
} elseif (is_numeric($val)) {
$val = 1 * $val;
} else {
$lval = strtolower($val);
$val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
}
if ($key === '') {
$items[] = $val;
} else {
$items[$key] = $val;
}
}
$value = count($items) < 2 && $key === '' ? $val : $items;
} else {
$value = trim($value);
if (is_numeric($value)) {
$value = 1 * $value;
} else {
$lval = strtolower($value);
$value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
}
}
$res[$name][] = $value;
}
return $res;
}
protected function match($subject, $pattern, $flags = 0, $offset = 0)
{
$matches = null;
if ($offset > strlen($subject)) {
return null;
}
return preg_match($pattern, $subject, $matches, $flags, $offset) ? $matches : null;
}
protected function matchAll($subject, $pattern, $flags = 0, $offset = 0)
{
$matches = null;
if ($offset > strlen($subject)) {
return [];
}
preg_match_all(
$pattern, $subject, $matches,
($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
$offset
);
return $matches;
}
}
class BaseAnnotation
{
/**
* Argument separator string
*/
const ARGUMENT_SEPARATOR = ' ';
/**
* Total number of arguments, excluding the comment (that should be at the end)
*/
const ARGUMENT_COUNT = 0;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $value;
/**
* @var array
*/
protected $params;
/**
* @var string
*/
protected $description;
function __construct($value)
{
$this->value = $value;
if (static::ARGUMENT_COUNT > 0) {
$maxParams = static::ARGUMENT_COUNT + 1;
$this->params = explode(static::ARGUMENT_SEPARATOR, $value, $maxParams);
if (count($this->params) == $maxParams) {
$this->description = array_pop($this->params);
}
} else {
$this->description = $value;
}
}
public function getName()
{
return $this->name;
}
public function getValue()
{
return $this->value;
}
public function getParameters()
{
return $this->params;
}
public function getDescription()
{
return $this->description;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment