4f74cc54a8d9ef24407c57bc3b2df31c29e63e8d
[civicrm-core.git] / Civi / Core / CiviEventInspector.php
1 <?php
2 namespace Civi\Core;
3
4 /**
5 * Class CiviEventInspector
6 *
7 * The event inspector is a development tool which provides metadata about events.
8 * It can be used for code-generators and documentation-generators.
9 *
10 * @code
11 * $i = new CiviEventInspector();
12 * print_r(CRM_Utils_Array::collect('name', $i->getAll()));
13 * @endCode
14 *
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 *
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 */
27 class CiviEventInspector {
28
29 /**
30 * Register the default hooks defined by 'CRM_Utils_Hook'.
31 *
32 * @param \Civi\Core\Event\GenericHookEvent $e
33 * @see \CRM_Utils_Hook::eventDefs()
34 */
35 public static function findBuiltInEvents(\Civi\Core\Event\GenericHookEvent $e) {
36 $skipList = array('singleton');
37 $e->inspector->addStaticStubs('CRM_Utils_Hook', 'hook_civicrm_',
38 function ($eventDef, $method) use ($skipList) {
39 return in_array($method->name, $skipList) ? NULL : $eventDef;
40 });
41 }
42
43 /**
44 * @var array
45 * Array(string $name => array $eventDef).
46 *
47 * Ex: $eventDefs['hook_civicrm_foo']['description_html'] = 'Hello world';
48 */
49 protected $eventDefs;
50
51 /**
52 * Perform a scan to identify/describe all events.
53 *
54 * @param bool $force
55 * @return CiviEventInspector
56 */
57 public function build($force = FALSE) {
58 if ($force || $this->eventDefs === NULL) {
59 $this->eventDefs = array();
60 \CRM_Utils_Hook::eventDefs($this);
61 ksort($this->eventDefs);
62 }
63 return $this;
64 }
65
66 /**
67 * Get a list of all events.
68 *
69 * @return array
70 * Array(string $name => array $eventDef).
71 * Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world';
72 */
73 public function getAll() {
74 $this->build();
75 return $this->eventDefs;
76 }
77
78 /**
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();
88 return array_filter($this->eventDefs, function ($e) use ($regex) {
89 return preg_match($regex, $e['name']);
90 });
91 }
92
93 /**
94 * Get the definition of one event.
95 *
96 * @param string $name
97 * Ex: 'hook_civicrm_alterSettingsMetaData'.
98 * @return array
99 * Ex: $result['description_html'] = 'Hello world';
100 */
101 public function get($name) {
102 $this->build();
103 return $this->eventDefs[$name];
104 }
105
106 /**
107 * @param $eventDef
108 * @return bool
109 * TRUE if valid.
110 */
111 public function validate($eventDef) {
112 if (!is_array($eventDef) || empty($eventDef['name']) || !isset($eventDef['type'])) {
113 return FALSE;
114 }
115
116 if (!in_array($eventDef['type'], array('hook', 'object'))) {
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;
127 }
128
129 /**
130 * Add a new event definition.
131 *
132 * @param array $eventDef
133 * @return CiviEventInspector
134 */
135 public function add($eventDef) {
136 $name = isset($eventDef['name']) ? $eventDef['name'] : NULL;
137
138 if (!isset($eventDef['type'])) {
139 $eventDef['type'] = preg_match('/^hook_/', $eventDef['name']) ? 'hook' : 'object';
140 }
141
142 if ($eventDef['type'] === 'hook' && empty($eventDef['signature'])) {
143 $eventDef['signature'] = implode(', ', array_map(
144 function ($field) {
145 $sigil = $field['ref'] ? '&$' : '$';
146 return $sigil . $field['name'];
147 },
148 $eventDef['fields']
149 ));
150 }
151
152 if (TRUE !== $this->validate($eventDef)) {
153 throw new \CRM_Core_Exception("Failed to register event ($name). Invalid definition.");
154 }
155
156 $this->eventDefs[$name] = $eventDef;
157 return $this;
158 }
159
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) {
170 $this->add(array(
171 'name' => $event,
172 'class' => $className,
173 ));
174 return $this;
175 }
176
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.
188 * @return CiviEventInspector
189 */
190 public function addStaticStubs($className, $prefix, $filter = NULL) {
191 $class = new \ReflectionClass($className);
192
193 foreach ($class->getMethods(\ReflectionMethod::IS_STATIC) as $method) {
194 if (!isset($method->name)) {
195 continue;
196 }
197
198 $eventDef = array(
199 'name' => $prefix . $method->name,
200 'description_html' => $method->getDocComment() ? \CRM_Admin_Page_APIExplorer::formatDocBlock($method->getDocComment()) : '',
201 'fields' => array(),
202 'class' => 'Civi\Core\Event\GenericHookEvent',
203 'stub' => $method,
204 );
205
206 foreach ($method->getParameters() as $parameter) {
207 $eventDef['fields'][$parameter->getName()] = array(
208 'name' => $parameter->getName(),
209 'ref' => (bool) $parameter->isPassedByReference(),
210 // WISHLIST: 'type' => 'mixed',
211 );
212 }
213
214 if ($filter !== NULL) {
215 $eventDef = $filter($eventDef, $method);
216 if ($eventDef === NULL) {
217 continue;
218 }
219 }
220
221 $this->add($eventDef);
222 }
223
224 return $this;
225 }
226
227 }