function filter_classes_by_notes($classes, $notes) { if (!is_array($notes)) { $notes = array($notes); } $parser = new \Nope\Parser(); $found = array(); foreach ($classes as $c) { $classNotes = $parser->parseClass(new \ReflectionClass($c)); foreach ($notes as $k) { if (isset($classNotes->notes[$k])) { $found[] = $c; } } } return $found; }
<?php require __DIR__ . '/../vendor/autoload.php'; /** * :foo = {}; * :bar = true; */ class Foo { /** :yep = false; */ public $pants; /** :yep = true; */ public function setWhoopee() { } } /** * :foo = ["z"]; * :baz = "DING"; */ class Bar extends Foo { /** :yep = true; */ public $pants; public function setWhoopee() { } } $p = new \Nope\Parser(); dump($p->parseHierarchy(Bar::class));
*/ namespace { goto classdefs; top: require "/home/bl/web/bm/big/config.php"; require __DIR__ . '/../vendor/autoload.php'; $iter = 10; $p = new Nope\FastParser(); $t = microtime(true); $cnt = 0; for ($i = 0; $i < $iter; $i++) { $cnt += $p->parseClass(\Big\CRM\Data\IOVersion::class) == true; } var_dump((microtime(true) - $t) / $iter * 1000); exit; $p = new Nope\Parser(); $t = microtime(true); $cnt = 0; for ($i = 0; $i < $iter; $i++) { $cnt += $p->parseClass(\Big\CRM\Data\IOVersion::class) == true; } var_dump((microtime(true) - $t) / $iter * 1000); exit; } namespace Nope { classdefs: class FastParser { const S_NONE = 0; const S_NAME = 1; const S_JSON = 2;
protected function loadMeta($class) { $ref = new \ReflectionClass($class); if (!$this->parser) { $this->parser = new \Nope\Parser(); } $notes = $this->parser->parseHierarchy($ref, \ReflectionProperty::IS_PUBLIC, \ReflectionMethod::IS_PUBLIC); $classNotes = $notes->notes; if (!isset($classNotes[$this->annotationNamespace])) { return null; } $info = $classNotes[$this->annotationNamespace]; if ($info === true) { $info = []; } table: if (!isset($info['table'])) { $info['table'] = $this->getDefaultTable($class); } class_relations: if (isset($info['relations'])) { foreach ($info['relations'] as $relKey => &$relDef) { $type = $relDef['type']; unset($relDef['type']); array_unshift($relDef, $type); if (!is_array($relDef)) { throw new \Exception("Relation {$relKey} was not valid in class {$class}"); } $relDef['mode'] = 'class'; } unset($relDef); } class_indexes: if (isset($info['indexes'])) { // TODO: should be in Meta so the Arrays mapper can share it foreach ($info['indexes'] as $idxKey => &$idxDef) { if ($idxDef === true) { $idxDef = ['fields' => [$idxKey]]; } } unset($idxDef); } foreach (array('property' => $notes->properties, 'method' => $notes->methods) as $noteType => $noteBag) { foreach ($noteBag as $name => $itemNotes) { if (!isset($itemNotes[$this->annotationNamespace])) { goto next_note_bag; } $itemNotes = $itemNotes[$this->annotationNamespace]; validate: if ($diff = array_diff(array_keys($itemNotes), ['has', 'field', 'constructor'])) { throw new \UnexpectedValueException("Invalid keys found in :amiss field/method annotation: " . implode(', ', $diff)); } if (isset($itemNotes['field']) && isset($itemNotes['has'])) { throw new \UnexpectedValueException("Invalid class {$class}: relation and a field declared together on {$name}"); } field: if (isset($itemNotes['field'])) { $field = $itemNotes['field']; // NOTE: do not ensure $field['name'] is set here - it happens later in // one big hit. // FIXME: this should also happen in the Meta, but we need to guarantee we // are operating on an array to collect getters and setters, etc if ($field === true) { $field = []; } elseif (is_string($field)) { $field = ['name' => $field]; } elseif ($field === false) { // allows us to remove properties from consideration in child classes, i.e. :amiss = {"field": false}; goto next_note_bag; } elseif (!is_array($field)) { throw new \UnexpectedValueException(); } if ($noteType == 'method') { $key = $this->fillGetterSetter($name, $field, !!'readOnly'); } else { $key = $name; } $manualKey = isset($field['id']) ? $field['id'] : null; unset($field['id']); $info['fields'][$manualKey ?: $key] = $field; } field_relation: if (isset($itemNotes['has'])) { $relation = $itemNotes['has']; if (is_string($relation)) { $relation = ["type" => $relation]; } elseif ($relation === false) { goto next_note_bag; } elseif (!is_array($relation)) { throw new \UnexpectedValueException(); } if (!isset($relation['type'])) { throw new \UnexpectedValueException("Relation {$name} missing 'type'"); } $type = $relation['type']; unset($relation['type']); array_unshift($relation, $type); if ($noteType == 'method') { $key = $this->fillGetterSetter($name, $relation, !!'readOnly'); } else { $key = $name; } $key = isset($relation['id']) ? $relation['id'] : $key; unset($relation['id']); if (isset($info['relations'][$key])) { throw new \UnexpectedValueException("Duplicate relation {$name} on class {$class}"); } $info['relations'][$key] = $relation; } constructor: if ($noteType == 'method' && isset($itemNotes['constructor'])) { if (isset($info['constructor']) && $info['constructor']) { throw new \UnexpectedValueException("Constructor already declared: {$info['constructor']}"); } $info['constructor'] = $name; if ($itemNotes['constructor'] !== true) { if (!is_array($itemNotes['constructor'])) { throw new \UnexpectedValueException(); } if (isset($info['constructorArgs']) && $info['constructorArgs']) { throw new \UnexpectedValueException("Constructor args declared at class level and also on method {$name}."); } $info['constructorArgs'] = $itemNotes['constructor']; } } next_note_bag: } } if (isset($info['fields'])) { $info['fields'] = $this->resolveUnnamedFields($info['fields']); } return new \Amiss\Meta(ltrim($class, '\\'), $info); }
} if (isset($groups['php'])) { foreach ($groups['php'] as $groupId => $group) { doctest_run_php_group($group); } } if (isset($blocks['php'])) { foreach ($blocks['php'] as $block) { if ($errors = php_lint_errors($block['code'])) { $errstr = implode("\n - ", $errors); throw new \RuntimeException("Linting block on {$block['file']}:{$block['line']} failed with errors:\n - " . $errstr); } } } lint_nope_blocks: $nope = new \Nope\Parser(); foreach ($allBlocks as $block) { if ($block['lang'] == 'php') { $tokens = token_get_all($block['code']); foreach ($tokens as $token) { if (is_array($token) && $token[0] == T_DOC_COMMENT) { $parsed = $nope->parseDocComment($token[1]); } } } elseif ($block['lang'] == 'nope') { $parsed = $nope->parseDocComment($block['code']); } } function doctest_run_php_group($group) { $scope = [];