Commit | Line | Data |
---|---|---|
bdd65b5c | 1 | <?php |
ec84755a | 2 | namespace Civi\Core; |
bdd65b5c TO |
3 | |
4 | /** | |
ec84755a | 5 | * Class CiviEventInspector |
bdd65b5c | 6 | * |
6f1818fd | 7 | * The event inspector is a development tool which provides metadata about events. |
bdd65b5c TO |
8 | * It can be used for code-generators and documentation-generators. |
9 | * | |
0b882a86 | 10 | * ``` |
ec84755a | 11 | * $i = new CiviEventInspector(); |
6f1818fd | 12 | * print_r(CRM_Utils_Array::collect('name', $i->getAll())); |
0b882a86 | 13 | * ``` |
bdd65b5c | 14 | * |
513d9c29 TO |
15 | * An event definition includes these fields: |
16 | * - type: string, required. Ex: 'hook' or 'object' | |
17 | * - name: string, required. Ex: 'hook_civicrm_post' or 'civi.dao.postInsert' | |
18 | * - class: string, required. Ex: 'Civi\Core\Event\GenericHookEvent'. | |
19 | * - signature: string, required FOR HOOKS. Ex: '$first, &$second'. | |
20 | * - fields: array, required FOR HOOKS. List of hook parameters. | |
21 | * - stub: ReflectionMethod, optional. An example function with docblocks/inputs. | |
22 | * | |
bdd65b5c TO |
23 | * Note: The inspector is only designed for use in developer workflows, such |
24 | * as code-generation and inspection. It should be not called by regular | |
25 | * runtime logic. | |
26 | */ | |
ec84755a | 27 | class CiviEventInspector { |
bdd65b5c TO |
28 | |
29 | /** | |
30 | * Register the default hooks defined by 'CRM_Utils_Hook'. | |
31 | * | |
32 | * @param \Civi\Core\Event\GenericHookEvent $e | |
47e7c2f8 | 33 | * @see \CRM_Utils_Hook::eventDefs() |
bdd65b5c | 34 | */ |
6f1818fd | 35 | public static function findBuiltInEvents(\Civi\Core\Event\GenericHookEvent $e) { |
c64f69d9 | 36 | $skipList = ['singleton']; |
bdd65b5c | 37 | $e->inspector->addStaticStubs('CRM_Utils_Hook', 'hook_civicrm_', |
6f1818fd TO |
38 | function ($eventDef, $method) use ($skipList) { |
39 | return in_array($method->name, $skipList) ? NULL : $eventDef; | |
bdd65b5c TO |
40 | }); |
41 | } | |
42 | ||
43 | /** | |
44 | * @var array | |
6f1818fd | 45 | * Array(string $name => array $eventDef). |
bdd65b5c | 46 | * |
6f1818fd | 47 | * Ex: $eventDefs['hook_civicrm_foo']['description_html'] = 'Hello world'; |
bdd65b5c | 48 | */ |
6f1818fd | 49 | protected $eventDefs; |
bdd65b5c TO |
50 | |
51 | /** | |
6f1818fd | 52 | * Perform a scan to identify/describe all events. |
bdd65b5c TO |
53 | * |
54 | * @param bool $force | |
ec84755a | 55 | * @return CiviEventInspector |
bdd65b5c TO |
56 | */ |
57 | public function build($force = FALSE) { | |
6f1818fd | 58 | if ($force || $this->eventDefs === NULL) { |
c64f69d9 | 59 | $this->eventDefs = []; |
47e7c2f8 | 60 | \CRM_Utils_Hook::eventDefs($this); |
6f1818fd | 61 | ksort($this->eventDefs); |
bdd65b5c TO |
62 | } |
63 | return $this; | |
64 | } | |
65 | ||
66 | /** | |
6f1818fd | 67 | * Get a list of all events. |
bdd65b5c TO |
68 | * |
69 | * @return array | |
6f1818fd TO |
70 | * Array(string $name => array $eventDef). |
71 | * Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world'; | |
bdd65b5c TO |
72 | */ |
73 | public function getAll() { | |
74 | $this->build(); | |
6f1818fd | 75 | return $this->eventDefs; |
bdd65b5c TO |
76 | } |
77 | ||
78 | /** | |
6f1818fd TO |
79 | * Find any events that match a pattern. |
80 | * | |
81 | * @param string $regex | |
82 | * @return array | |
83 | * Array(string $name => array $eventDef). | |
84 | * Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world'; | |
85 | */ | |
86 | public function find($regex) { | |
87 | $this->build(); | |
0d0031a0 | 88 | return array_filter($this->eventDefs, function ($e) use ($regex) { |
6f1818fd TO |
89 | return preg_match($regex, $e['name']); |
90 | }); | |
91 | } | |
92 | ||
93 | /** | |
94 | * Get the definition of one event. | |
bdd65b5c TO |
95 | * |
96 | * @param string $name | |
97 | * Ex: 'hook_civicrm_alterSettingsMetaData'. | |
98 | * @return array | |
6f1818fd | 99 | * Ex: $result['description_html'] = 'Hello world'; |
bdd65b5c TO |
100 | */ |
101 | public function get($name) { | |
102 | $this->build(); | |
6f1818fd | 103 | return $this->eventDefs[$name]; |
bdd65b5c TO |
104 | } |
105 | ||
106 | /** | |
6f1818fd | 107 | * @param $eventDef |
bdd65b5c TO |
108 | * @return bool |
109 | * TRUE if valid. | |
110 | */ | |
6f1818fd | 111 | public function validate($eventDef) { |
0d0031a0 TO |
112 | if (!is_array($eventDef) || empty($eventDef['name']) || !isset($eventDef['type'])) { |
113 | return FALSE; | |
114 | } | |
115 | ||
c64f69d9 | 116 | if (!in_array($eventDef['type'], ['hook', 'object'])) { |
0d0031a0 TO |
117 | return FALSE; |
118 | } | |
119 | ||
120 | if ($eventDef['type'] === 'hook') { | |
121 | if (!isset($eventDef['signature']) || !is_array($eventDef['fields'])) { | |
122 | return FALSE; | |
123 | } | |
124 | } | |
125 | ||
126 | return TRUE; | |
bdd65b5c TO |
127 | } |
128 | ||
129 | /** | |
6f1818fd | 130 | * Add a new event definition. |
bdd65b5c | 131 | * |
6f1818fd | 132 | * @param array $eventDef |
ec84755a | 133 | * @return CiviEventInspector |
bdd65b5c | 134 | */ |
6f1818fd | 135 | public function add($eventDef) { |
2e1f50d6 | 136 | $name = $eventDef['name'] ?? NULL; |
6f1818fd | 137 | |
0d0031a0 TO |
138 | if (!isset($eventDef['type'])) { |
139 | $eventDef['type'] = preg_match('/^hook_/', $eventDef['name']) ? 'hook' : 'object'; | |
6f1818fd | 140 | } |
bdd65b5c | 141 | |
0d0031a0 | 142 | if ($eventDef['type'] === 'hook' && empty($eventDef['signature'])) { |
6f1818fd | 143 | $eventDef['signature'] = implode(', ', array_map( |
bdd65b5c TO |
144 | function ($field) { |
145 | $sigil = $field['ref'] ? '&$' : '$'; | |
146 | return $sigil . $field['name']; | |
147 | }, | |
6f1818fd | 148 | $eventDef['fields'] |
bdd65b5c TO |
149 | )); |
150 | } | |
151 | ||
6f1818fd TO |
152 | if (TRUE !== $this->validate($eventDef)) { |
153 | throw new \CRM_Core_Exception("Failed to register event ($name). Invalid definition."); | |
bdd65b5c TO |
154 | } |
155 | ||
6f1818fd | 156 | $this->eventDefs[$name] = $eventDef; |
bdd65b5c TO |
157 | return $this; |
158 | } | |
159 | ||
0d0031a0 TO |
160 | /** |
161 | * Scan a Symfony event class for metadata, and add it. | |
162 | * | |
163 | * @param string $event | |
164 | * Ex: 'civi.api.authorize'. | |
165 | * @param string $className | |
166 | * Ex: 'Civi\API\Event\AuthorizeEvent'. | |
167 | * @return CiviEventInspector | |
168 | */ | |
169 | public function addEventClass($event, $className) { | |
c64f69d9 | 170 | $this->add([ |
0d0031a0 TO |
171 | 'name' => $event, |
172 | 'class' => $className, | |
c64f69d9 | 173 | ]); |
0d0031a0 TO |
174 | return $this; |
175 | } | |
176 | ||
bdd65b5c TO |
177 | /** |
178 | * Scan a class for hook stubs, and add all of them. | |
179 | * | |
180 | * @param string $className | |
181 | * The name of a class which contains static stub functions. | |
182 | * Ex: 'CRM_Utils_Hook'. | |
183 | * @param string $prefix | |
184 | * A prefix to apply to all hook names. | |
185 | * Ex: 'hook_civicrm_'. | |
186 | * @param null|callable $filter | |
187 | * An optional function to filter/rewrite the metadata for each hook. | |
ec84755a | 188 | * @return CiviEventInspector |
bdd65b5c | 189 | */ |
6f1818fd | 190 | public function addStaticStubs($className, $prefix, $filter = NULL) { |
ec84755a | 191 | $class = new \ReflectionClass($className); |
bdd65b5c | 192 | |
ec84755a | 193 | foreach ($class->getMethods(\ReflectionMethod::IS_STATIC) as $method) { |
bdd65b5c TO |
194 | if (!isset($method->name)) { |
195 | continue; | |
196 | } | |
197 | ||
c64f69d9 | 198 | $eventDef = [ |
bdd65b5c | 199 | 'name' => $prefix . $method->name, |
ec84755a | 200 | 'description_html' => $method->getDocComment() ? \CRM_Admin_Page_APIExplorer::formatDocBlock($method->getDocComment()) : '', |
c64f69d9 | 201 | 'fields' => [], |
bdd65b5c | 202 | 'class' => 'Civi\Core\Event\GenericHookEvent', |
e1d66df7 | 203 | 'stub' => $method, |
c64f69d9 | 204 | ]; |
bdd65b5c TO |
205 | |
206 | foreach ($method->getParameters() as $parameter) { | |
c64f69d9 | 207 | $eventDef['fields'][$parameter->getName()] = [ |
bdd65b5c TO |
208 | 'name' => $parameter->getName(), |
209 | 'ref' => (bool) $parameter->isPassedByReference(), | |
210 | // WISHLIST: 'type' => 'mixed', | |
c64f69d9 | 211 | ]; |
bdd65b5c TO |
212 | } |
213 | ||
214 | if ($filter !== NULL) { | |
6f1818fd TO |
215 | $eventDef = $filter($eventDef, $method); |
216 | if ($eventDef === NULL) { | |
bdd65b5c TO |
217 | continue; |
218 | } | |
219 | } | |
220 | ||
6f1818fd | 221 | $this->add($eventDef); |
bdd65b5c TO |
222 | } |
223 | ||
224 | return $this; | |
225 | } | |
226 | ||
227 | } |