Completed
Pull Request — patch_1-1-7 (#3421)
by Spuds
13:53
created

Event_Manager::_getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Handle events in controller and classes
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * @version 1.1.7
11
 */
12
13
class Event_Manager
14
{
15
	/**
16
	 * An array of events, each entry is a different position.
17
	 * @var object[] Event
18
	 */
19
	protected $_registered_events = array();
20
21
	/**
22
	 * Instances of addons already loaded.
23
	 * @var object[]
24
	 */
25
	protected $_instances = array();
26
27
	/**
28
	 * Instances of the controller.
29
	 * @var object
30
	 */
31
	protected $_source = null;
32
33
	/**
34
	 * List of classes already registered.
35
	 * @var string[]
36
	 */
37
	protected $_classes = array();
38
39
	/**
40
	 * List of classes declared, kept here just to avoid
41
	 * call get_declared_classes at each trigger
42
	 * @var null|string[]
43
	 */
44
	protected $_declared_classes = null;
45
46
	/**
47
	 * Just a dummy for the time being.
48
	 */
49
	public function __construct()
50
	{
51
	}
52
53
	/**
54
	 * Allows to set the object that instantiated the Event_Manager.
55
	 *
56
	 * - Necessary in order to be able to provide the dependencies later on
57
	 *
58
	 * @param object $source The controller that instantiated the Event_Manager
59
	 */
60
	public function setSource($source)
61
	{
62
		$this->_source = $source;
63
	}
64
65
	/**
66
	 * This is the function use to... trigger an event.
67
	 *
68
	 * - Called from many areas in the code where events can be raised
69
	 * $this->_events->trigger('area', args)
70
	 *
71
	 * @param string $position The "identifier" of the event, such as prepare_post
72
	 * @param mixed[] $args The arguments passed to the methods registered
73
	 */
74
	public function trigger($position, $args = array())
75
	{
76
		// Nothing registered against this event, just return
77
		if (!array_key_exists($position, $this->_registered_events) || !$this->_registered_events[$position]->hasEvents())
78
			return false;
79
80
		// For all areas that that registered against this event, let them know its been triggered
81
		foreach ($this->_registered_events[$position]->getEvents() as $event)
82
		{
83
			$class = $event[1];
84
			$class_name = $class[0];
85
			$method_name = $class[1];
86
			$deps = isset($event[2]) ? $event[2] : array();
87
			$dependencies = null;
88
89
			if (!class_exists($class_name))
90
				return false;
91
92
			// Any dependency you want? In any order you want!
93
			if (!empty($deps))
94
			{
95
				foreach ($deps as $dep)
96
				{
97
					if (array_key_exists($dep, $args))
98
						$dependencies[$dep] = &$args[$dep];
99
					else
100
						$this->_source->provideDependencies($dep, $dependencies);
101
				}
102
			}
103
			else
104
			{
105
				foreach ($args as $key => $val)
106
					$dependencies[$key] = &$args[$key];
107
			}
108
109
			$instance = $this->_getInstance($class_name);
110
111
			// Do what we know we should do... if we find it.
112
			if (is_callable(array($instance, $method_name)))
113
			{
114
				// Don't send $dependencies if there are none / the method can't use them
115
				if (empty($dependencies))
116
					call_user_func(array($instance, $method_name));
117
				else
118
				{
119
					$this->_checkParameters($class_name, $method_name, $dependencies);
120
					call_user_func_array(array($instance, $method_name), $dependencies);
121
				}
122
			}
123
		}
124
	}
125
126
	/**
127
	 * Retrieves or creates the instance of an object.
128
	 *
129
	 * What it does:
130
	 *
131
	 * - Objects are stored in order to be shared between different triggers
132
	 * in the same Event_Manager.
133
	 * - If the object doesn't exist yet, it is created
134
	 *
135
	 * @param string $class_name The name of the class.
136
	 * @return An instance of the class requested.
137
	 */
138
	protected function _getInstance($class_name)
139
	{
140
		if (isset($this->_instances[$class_name]))
141
			return $this->_instances[$class_name];
142
		else
143
		{
144
			$instance = new $class_name(HttpReq::instance());
145
			$this->_setInstance($class_name, $instance);
146
147
			return $instance;
148
		}
149
	}
150
151
	/**
152
	 * Stores the instance of a class created by _getInstance.
153
	 *
154
	 * @param string $class_name The name of the class.
155
	 * @param object $instance The object.
156
	 */
157
	protected function _setInstance($class_name, $instance)
158
	{
159
		if (!isset($this->_instances[$class_name]))
160
			$this->_instances[$class_name] = $instance;
161
	}
162
163
	/**
164
	 * Registers an event at a certain position with a defined priority.
165
	 *
166
	 * @param string $position The position at which the event will be triggered
167
	 * @param mixed[] $event An array describing the event we want to trigger:
168
	 *   0 => string - the position at which the event will be triggered
169
	 *   1 => string[] - the class and method we want to call:
170
	 *      array(
171
	 *        0 => string - name of the class to instantiate
172
	 *        1 => string - name of the method to call
173
	 *      )
174
	 *   2 => null|string[] - an array of dependencies in the form of strings representing the
175
	 *        name of the variables the method requires.
176
	 *        The variables can be from:
177
	 *          - the default list of variables passed to the trigger
178
	 *          - properties (private, protected, or public) of the object that instantiate the Event_Manager
179
	 *            (i.e. the controller)
180
	 *          - globals
181
	 * @param int $priority Defines the order the method is called.
182
	 */
183
	public function register($position, $event, $priority = 0)
184
	{
185
		if (!array_key_exists($position, $this->_registered_events))
186
			$this->_registered_events[$position] = new Event(new Priority());
0 ignored issues
show
Bug introduced by
new Priority() of type Priority is incompatible with the type EventBase expected by parameter $base of Event::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

186
			$this->_registered_events[$position] = new Event(/** @scrutinizer ignore-type */ new Priority());
Loading history...
Bug introduced by
The call to Event::__construct() has too few arguments starting with fd. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
			$this->_registered_events[$position] = /** @scrutinizer ignore-call */ new Event(new Priority());

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
187
188
		$this->_registered_events[$position]->add($event, $priority);
189
	}
190
191
	/**
192
	 * Loads addons and modules based on a pattern.
193
	 *
194
	 * - The pattern defines the names of the classes that will be registered
195
	 * to this Event_Manager.
196
	 *
197
	 * @param string[] $classes A set of class names that should be attached
198
	 */
199
	public function registerClasses($classes)
200
	{
201
		$this->_register_events($classes);
202
	}
203
204
	/**
205
	 * Gets the names of all the classes already loaded.
206
	 *
207
	 * @return string[]
208
	 */
209
	protected function _declared_classes()
210
	{
211
		if ($this->_declared_classes === null)
212
			$this->_declared_classes = get_declared_classes();
213
214
		return $this->_declared_classes;
215
	}
216
217
	/**
218
	 * Takes care of registering the classes/methods to the different positions
219
	 * of the Event_Manager.
220
	 *
221
	 * What it does:
222
	 *
223
	 * - Each class must have a static Method ::hooks
224
	 * - Method ::hooks must return an array defining where and how the class
225
	 * will interact with the object that started the Event_Manager.
226
	 *
227
	 * @param string[] $classes A list of class names.
228
	 */
229
	protected function _register_events($classes)
230
	{
231
		foreach ($classes as $class)
232
		{
233
			// Load the events for this area/class combination
234
			$events = $class::hooks($this);
235
			if (is_array($events) === false)
236
			{
237
				continue;
238
			}
239
240
			foreach ($events as $event)
241
			{
242
				// Check if a priority (ordering) was specified
243
				$priority = isset($event[1][2]) ? $event[1][2] : 0;
244
				$position = $event[0];
245
246
				// Register the "action" to take when the event is triggered
247
				$this->register($position, $event, $priority);
248
			}
249
		}
250
	}
251
252
	/**
253
	 * Reflects a specific class method to see what parameters are needed
254
	 *
255
	 * Currently only checks on number required, can be expanded to make use of
256
	 * $params = $r->getParameters() and then $param-> getName isOptional etc
257
	 * to ensure required named are being passed.
258
	 *
259
	 * @param string $class_name
260
	 * @param string $method_name
261
	 * @param array $dependencies the dependencies the event registered
262
	 *
263
	 */
264
	protected function _checkParameters($class_name, $method_name, &$dependencies)
265
	{
266
		// Lets check on the actual methods parameters
267
		try
268
		{
269
			$r = new ReflectionMethod($class_name, $method_name);
270
			$number_params = $r->getNumberOfParameters();
271
			unset($r);
272
		}
273
		catch (\Exception $e)
274
		{
275
			$number_params = 0;
276
		}
277
278
		// Php8 will not like passing parameters to a method that takes none
279
		if ($number_params == 0 && !empty($dependencies))
280
		{
281
			$dependencies = array();
282
		}
283
	}
284
}
285