102b1bc40e15f44afc20a95048983dd05983ee95
[civicrm-core.git] / Civi / Api4 / Utils / ReflectionUtils.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 namespace Civi\Api4\Utils;
13
14 /**
15 * Just another place to put static functions...
16 */
17 class ReflectionUtils {
18
19 /**
20 * @param \Reflector|\ReflectionClass $reflection
21 * @param string $type
22 * If we are not reflecting the class itself, specify "Method", "Property", etc.
23 * @param array $vars
24 * Variable substitutions to perform in the docblock
25 * @return array
26 */
27 public static function getCodeDocs($reflection, $type = NULL, $vars = []) {
28 $comment = $reflection->getDocComment();
29 foreach ($vars as $key => $val) {
30 $comment = str_replace('$' . strtoupper(\CRM_Utils_String::pluralize($key)), \CRM_Utils_String::pluralize($val), $comment);
31 $comment = str_replace('$' . strtoupper($key), $val, $comment);
32 }
33 $docs = self::parseDocBlock($comment);
34
35 // Recurse into parent functions
36 if (isset($docs['inheritDoc']) || isset($docs['inheritdoc'])) {
37 unset($docs['inheritDoc'], $docs['inheritdoc']);
38 $newReflection = NULL;
39 try {
40 if ($type) {
41 $name = $reflection->getName();
42 $reflectionClass = $reflection->getDeclaringClass()->getParentClass();
43 if ($reflectionClass) {
44 $getItem = "get$type";
45 $newReflection = $reflectionClass->$getItem($name);
46 }
47 }
48 else {
49 $newReflection = $reflection->getParentClass();
50 }
51 }
52 catch (\ReflectionException $e) {
53 }
54 if ($newReflection) {
55 // Mix in
56 $additionalDocs = self::getCodeDocs($newReflection, $type, $vars);
57 if (!empty($docs['comment']) && !empty($additionalDocs['comment'])) {
58 $docs['comment'] .= "\n\n" . $additionalDocs['comment'];
59 }
60 $docs += $additionalDocs;
61 }
62 }
63 return $docs;
64 }
65
66 /**
67 * @param string $comment
68 * @return array
69 */
70 public static function parseDocBlock($comment) {
71 $info = [];
72 $param = NULL;
73 foreach (preg_split("/((\r?\n)|(\r\n?))/", $comment) as $num => $line) {
74 if (!$num || strpos($line, '*/') !== FALSE) {
75 continue;
76 }
77 $line = ltrim(trim($line), '*');
78 if (strlen($line) && $line[0] === ' ') {
79 $line = substr($line, 1);
80 }
81 if (strpos(ltrim($line), '@') === 0) {
82 $words = explode(' ', ltrim($line, ' @'));
83 $key = array_shift($words);
84 $param = NULL;
85 if ($key == 'var') {
86 $info['type'] = explode('|', $words[0]);
87 }
88 elseif ($key == 'return') {
89 $info['return'] = explode('|', $words[0]);
90 }
91 elseif ($key == 'options' || $key == 'ui_join_filters') {
92 $val = str_replace(', ', ',', implode(' ', $words));
93 $info[$key] = explode(',', $val);
94 }
95 elseif ($key == 'throws' || $key == 'see') {
96 $info[$key][] = implode(' ', $words);
97 }
98 elseif ($key == 'searchable') {
99 $info[$key] = strtolower($words[0]) !== 'false';
100 }
101 elseif ($key == 'param' && $words) {
102 $type = $words[0][0] !== '$' ? explode('|', array_shift($words)) : NULL;
103 $param = rtrim(array_shift($words), '-:()/');
104 $info['params'][$param] = [
105 'type' => $type,
106 'description' => $words ? ltrim(implode(' ', $words), '-: ') : '',
107 'comment' => '',
108 ];
109 }
110 else {
111 // Unrecognized annotation, but we'll duly add it to the info array
112 $val = implode(' ', $words);
113 $info[$key] = strlen($val) ? $val : TRUE;
114 }
115 }
116 elseif ($param) {
117 $info['params'][$param]['comment'] .= $line . "\n";
118 }
119 elseif ($num == 1) {
120 $info['description'] = ucfirst($line);
121 }
122 elseif (!$line) {
123 if (isset($info['comment'])) {
124 $info['comment'] .= "\n";
125 }
126 else {
127 $info['comment'] = NULL;
128 }
129 }
130 // For multi-line description.
131 elseif (count($info) === 1 && isset($info['description']) && substr($info['description'], -1) !== '.') {
132 $info['description'] .= ' ' . $line;
133 }
134 else {
135 $info['comment'] = isset($info['comment']) ? "{$info['comment']}\n$line" : $line;
136 }
137 }
138 if (isset($info['comment'])) {
139 $info['comment'] = rtrim($info['comment']);
140 }
141 return $info;
142 }
143
144 /**
145 * List all traits used by a class and its parents.
146 *
147 * @param object|string $class
148 * @return array
149 */
150 public static function getTraits($class) {
151 $traits = [];
152 // Get traits of this class + parent classes
153 do {
154 $traits = array_merge(class_uses($class), $traits);
155 } while ($class = get_parent_class($class));
156 // Get traits of traits
157 foreach ($traits as $trait => $same) {
158 $traits = array_merge(class_uses($trait), $traits);
159 }
160 return $traits;
161 }
162
163 }