f4dc1c6e2a8e7e33302abbc86e5d8083fdd759d7
[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 * Note: The inspector is only designed for use in developer workflows, such
16 * as code-generation and inspection. It should be not called by regular
17 * runtime logic.
18 */
19 class CiviEventInspector {
20
21 /**
22 * Register the default hooks defined by 'CRM_Utils_Hook'.
23 *
24 * @param \Civi\Core\Event\GenericHookEvent $e
25 * @see \CRM_Utils_Hook::hooks()
26 */
27 public static function findBuiltInEvents(\Civi\Core\Event\GenericHookEvent $e) {
28 $skipList = array('singleton');
29 $e->inspector->addStaticStubs('CRM_Utils_Hook', 'hook_civicrm_',
30 function ($eventDef, $method) use ($skipList) {
31 return in_array($method->name, $skipList) ? NULL : $eventDef;
32 });
33 }
34
35 /**
36 * @var array
37 * Array(string $name => array $eventDef).
38 *
39 * Ex: $eventDefs['hook_civicrm_foo']['description_html'] = 'Hello world';
40 */
41 protected $eventDefs;
42
43 /**
44 * Perform a scan to identify/describe all events.
45 *
46 * @param bool $force
47 * @return CiviEventInspector
48 */
49 public function build($force = FALSE) {
50 if ($force || $this->eventDefs === NULL) {
51 $this->eventDefs = array();
52 \CRM_Utils_Hook::hooks($this);
53 ksort($this->eventDefs);
54 }
55 return $this;
56 }
57
58 /**
59 * Get a list of all events.
60 *
61 * @return array
62 * Array(string $name => array $eventDef).
63 * Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world';
64 */
65 public function getAll() {
66 $this->build();
67 return $this->eventDefs;
68 }
69
70 /**
71 * Find any events that match a pattern.
72 *
73 * @param string $regex
74 * @return array
75 * Array(string $name => array $eventDef).
76 * Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world';
77 */
78 public function find($regex) {
79 $this->build();
80 return array_filter($this->eventDefs, function($e) use ($regex) {
81 return preg_match($regex, $e['name']);
82 });
83 }
84
85 /**
86 * Get the definition of one event.
87 *
88 * @param string $name
89 * Ex: 'hook_civicrm_alterSettingsMetaData'.
90 * @return array
91 * Ex: $result['description_html'] = 'Hello world';
92 */
93 public function get($name) {
94 $this->build();
95 return $this->eventDefs[$name];
96 }
97
98 /**
99 * @param $eventDef
100 * @return bool
101 * TRUE if valid.
102 */
103 public function validate($eventDef) {
104 return
105 is_array($eventDef)
106 && !empty($eventDef['name'])
107 && isset($eventDef['signature'])
108 && is_array($eventDef['fields']);
109 }
110
111 /**
112 * Add a new event definition.
113 *
114 * @param array $eventDef
115 * @return CiviEventInspector
116 */
117 public function add($eventDef) {
118 $name = isset($eventDef['name']) ? $eventDef['name'] : NULL;
119
120 if (!isset($eventDef['is_hook'])) {
121 $eventDef['is_hook'] = (bool) preg_match('/^hook_/', $eventDef['name']);
122 }
123
124 if (empty($eventDef['signature'])) {
125 $eventDef['signature'] = implode(', ', array_map(
126 function ($field) {
127 $sigil = $field['ref'] ? '&$' : '$';
128 return $sigil . $field['name'];
129 },
130 $eventDef['fields']
131 ));
132 }
133
134 if (TRUE !== $this->validate($eventDef)) {
135 throw new \CRM_Core_Exception("Failed to register event ($name). Invalid definition.");
136 }
137
138 $this->eventDefs[$name] = $eventDef;
139 return $this;
140 }
141
142 /**
143 * Scan a class for hook stubs, and add all of them.
144 *
145 * @param string $className
146 * The name of a class which contains static stub functions.
147 * Ex: 'CRM_Utils_Hook'.
148 * @param string $prefix
149 * A prefix to apply to all hook names.
150 * Ex: 'hook_civicrm_'.
151 * @param null|callable $filter
152 * An optional function to filter/rewrite the metadata for each hook.
153 * @return CiviEventInspector
154 */
155 public function addStaticStubs($className, $prefix, $filter = NULL) {
156 $class = new \ReflectionClass($className);
157
158 foreach ($class->getMethods(\ReflectionMethod::IS_STATIC) as $method) {
159 if (!isset($method->name)) {
160 continue;
161 }
162
163 $eventDef = array(
164 'name' => $prefix . $method->name,
165 'description_html' => $method->getDocComment() ? \CRM_Admin_Page_APIExplorer::formatDocBlock($method->getDocComment()) : '',
166 'fields' => array(),
167 'class' => 'Civi\Core\Event\GenericHookEvent',
168 );
169
170 foreach ($method->getParameters() as $parameter) {
171 $eventDef['fields'][$parameter->getName()] = array(
172 'name' => $parameter->getName(),
173 'ref' => (bool) $parameter->isPassedByReference(),
174 // WISHLIST: 'type' => 'mixed',
175 );
176 }
177
178 if ($filter !== NULL) {
179 $eventDef = $filter($eventDef, $method);
180 if ($eventDef === NULL) {
181 continue;
182 }
183 }
184
185 $this->add($eventDef);
186 }
187
188 return $this;
189 }
190
191 }