Passed
Pull Request — patch_1-1-7 (#3421)
by Spuds
37:02 queued 22:02
created

Event_Manager::_checkParameters()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
nc 3
nop 2
dl 0
loc 15
rs 10
c 1
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) || $this->_checkParameters($class_name, $method_name) === 0)
116
					call_user_func(array($instance, $method_name));
117
				else
118
					call_user_func_array(array($instance, $method_name), $dependencies);
119
			}
120
		}
121
	}
122
123
	/**
124
	 * Retrieves or creates the instance of an object.
125
	 *
126
	 * What it does:
127
	 *
128
	 * - Objects are stored in order to be shared between different triggers
129
	 * in the same Event_Manager.
130
	 * - If the object doesn't exist yet, it is created
131
	 *
132
	 * @param string $class_name The name of the class.
133
	 * @return An instance of the class requested.
134
	 */
135
	protected function _getInstance($class_name)
136
	{
137
		if (isset($this->_instances[$class_name]))
138
			return $this->_instances[$class_name];
139
		else
140
		{
141
			$instance = new $class_name(HttpReq::instance());
142
			$this->_setInstance($class_name, $instance);
143
144
			return $instance;
145
		}
146
	}
147
148
	/**
149
	 * Stores the instance of a class created by _getInstance.
150
	 *
151
	 * @param string $class_name The name of the class.
152
	 * @param object $instance The object.
153
	 */
154
	protected function _setInstance($class_name, $instance)
155
	{
156
		if (!isset($this->_instances[$class_name]))
157
			$this->_instances[$class_name] = $instance;
158
	}
159
160
	/**
161
	 * Registers an event at a certain position with a defined priority.
162
	 *
163
	 * @param string $position The position at which the event will be triggered
164
	 * @param mixed[] $event An array describing the event we want to trigger:
165
	 *   0 => string - the position at which the event will be triggered
166
	 *   1 => string[] - the class and method we want to call:
167
	 *      array(
168
	 *        0 => string - name of the class to instantiate
169
	 *        1 => string - name of the method to call
170
	 *      )
171
	 *   2 => null|string[] - an array of dependencies in the form of strings representing the
172
	 *        name of the variables the method requires.
173
	 *        The variables can be from:
174
	 *          - the default list of variables passed to the trigger
175
	 *          - properties (private, protected, or public) of the object that instantiate the Event_Manager
176
	 *            (i.e. the controller)
177
	 *          - globals
178
	 * @param int $priority Defines the order the method is called.
179
	 */
180
	public function register($position, $event, $priority = 0)
181
	{
182
		if (!array_key_exists($position, $this->_registered_events))
183
			$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

183
			$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

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