Completed
Push — main ( 169704 )
by
unknown
24s queued 19s
created

TBehaviorsModule::setAdditionalBehaviors()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 14
c 1
b 0
f 0
nc 16
nop 1
dl 0
loc 20
rs 8.0555
1
<?php
2
/**
3
 * TBehaviorsModule class
4
 *
5
 * @author Brad Anderson <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Util;
11
12
use Prado\Exceptions\TConfigurationException;
13
use Prado\Exceptions\TInvalidDataTypeException;
14
use Prado\TApplication;
15
use Prado\TComponent;
16
use Prado\Xml\TXmlDocument;
17
use Prado\Xml\TXmlElement;
18
19
/**
20
 * TBehaviorsModule class.
21
 *
22
 * TBehaviorsModule loads and attaches {@link TBehavior}.  This attaches
23
 * Behaviors to classes and to application components like the TApplication,
24
 * individual modules, and TPage of the TPageService.
25
 *
26
 * Contents enclosed within the module tag are treated as behaviors, e.g.,
27
 * <code>
28
 * <module class="Prado\Util\TBehaviorsModule" Parameter="AdditionalBehaviors">
29
 *   <behavior Name="pagethemeparameter" Class="Prado\Util\Behaviors\TParameterizeBehavior" AttachToClass="Prade\Web\UI\TPage" Priority="10" Parameter="ThemeName" Property="Theme"/>
30
 *   <behavior Name="sharedModuleBehavior" Class="FooModuleBehavior" AttachToClass="Prado\TModule" Attribute1="abc"/>
31
 *   <behavior name="TimeZoneBehavior" Class="Prado\Util\Behaviors\TTimeZoneParameterBehavior" AttachTo="Application" Priority="10" TimeZone="America/New York" TimeZoneParameter="prop:TimeZone" />
32
 *   <behavior name="MyModuleBehavior" Class="MyModuleBehavior" AttachTo="Module:page" Property1="Value1" Property2="Value2" ... />
33
 *   <behavior name="MyPageTitleBehavior" Class="Prado\Util\Behaviors\TParameterizeBehavior" AttachTo="Page" Priority="10" Parameter="PageTitle" Property="Title" Localize="true"/>
34
 * </module>
35
 * </code>
36
 *
37
 * When the Service is not TPageService, page behaviors are not installed and have no effect other than be ignored.
38
 *
39
 * When {@link setAdditionalBehaviors AdditionalBehaviors} is set, this module
40
 * loads the behaviors from that property. It can be an array of php behavior definition arrays.
41
 * or a string that is then passed through unserialize or json_decode; otherwise is treated as
42
 * an xml document of behavior like above.
43
 *
44
 * The format is in the PHP style module configuration:
45
 * </code>
46
 *		[['name' => 'behaviorname', 'class' => 'TMyBehaviorClass', 'attachto' => 'page', 'priority' => '10', 'behaviorProperty'=>"value1'], ...]
47
 * </code>
48
 *
49
 * This allows TBehaviorsModule to load behaviors, dynamically, from parameters with the TParameterizeBehavior.
50
 *
51
 * Multiple TBehaviorsModules can be instanced, for instance, a second set of behaviors
52
 * could be configured in TPageService.  The second TBehaviorsModule can reference
53
 * behaviors from the first to attach its behaviors.
54
 *
55
 * @author Brad Anderson <[email protected]>
56
 * @since 4.2.0
57
 */
58
class TBehaviorsModule extends \Prado\TModule
59
{
60
	/**
61
	 * @var bool wether or not WeakReference is available
62
	 */
63
	private static $_weak;
64
65
	/**
66
	 * @var IBaseBehavior[] loaded behaviors.
67
	 */
68
	private static $_behaviors = [];
69
70
	/**
71
	 * @var IBaseBehavior[] behaviors attaching to the TPage
72
	 */
73
	private $_pageBehaviors = [];
74
75
	/**
76
	 * @var array[] delayed behaviors attaching to the loaded behaviors
77
	 */
78
	private $_behaviorBehaviors = [];
79
80
	/**
81
	 * @var array[] additional behaviors in a configuration format: array[], serialized php object, json object, string of xml
82
	 */
83
	private $_additionalBehaviors;
84
85
	/**
86
	 * Constructor.
87
	 *  Discovers the availability of the {@link WeakReference} object in PHP 7.4.0+.
88
	 */
89
	public function __construct()
90
	{
91
		if (self::$_weak === null) {
0 ignored issues
show
introduced by
The condition self::_weak === null is always false.
Loading history...
92
			self::$_weak = class_exists('\WeakReference', false);
93
		}
94
		parent::__construct();
95
	}
96
97
	/**
98
	 * Initializes the module by loading behaviors.  If there are page behaviors, this
99
	 * attaches behaviors to TPage through TApplication::onBeginRequest and then
100
	 * TPageService::onPreRunPage.
101
	 * @param \Prado\Xml\TXmlElement $config configuration for this module, can be null
102
	 */
103
	public function init($config)
104
	{
105
		$this->loadBehaviors($config);
106
		$this->loadBehaviors(['behaviors' => $this->getAdditionalBehaviors()]);
107
108
		if (count($this->_pageBehaviors)) {
109
			$this->getApplication()->attachEventHandler('onBeginRequest', [$this, 'attachTPageServiceHandler']);
110
		}
111
		foreach ($this->_behaviorBehaviors as $target => $behaviors) {
112
			if (!isset(self::$_behaviors[$target])) {
113
				throw new TConfigurationException('behaviormodule_behaviorowner_required', 'behavior:' . $target);
114
			}
115
			$owner = self::$_behaviors[$target];
116
			$owner = is_a($owner, '\WeakReference') ? $owner->get() : $owner;
0 ignored issues
show
Bug introduced by
The method get() does not exist on Prado\Util\IBaseBehavior. It seems like you code against a sub-type of Prado\Util\IBaseBehavior such as Prado\Util\TBehavior or Prado\Util\TClassBehavior. ( Ignorable by Annotation )

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

116
			$owner = is_a($owner, '\WeakReference') ? $owner->/** @scrutinizer ignore-call */ get() : $owner;
Loading history...
117
			foreach ($behaviors as $properties) {
118
				$priority = $properties['priority'] ?? null;
119
				unset($properties['priority']);
120
				$name = $properties['name'];
121
				unset($properties['name']);
122
				$owner->attachBehavior($name, $properties, $priority);
0 ignored issues
show
Bug introduced by
The method attachBehavior() does not exist on Prado\Util\IBaseBehavior. Did you maybe mean attach()? ( Ignorable by Annotation )

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

122
				$owner->/** @scrutinizer ignore-call */ 
123
            attachBehavior($name, $properties, $priority);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
123
			}
124
		}
125
		$this->_behaviorBehaviors = [];
126
		parent::init($config);
127
	}
128
129
	/**
130
	 * TApplication::onBeginRequest Handler that adds {@link attachTPageBehaviors} to
131
	 * TPageService::onPreRunPage. In turn, this attaches {@link attachTPageBehaviors}
132
	 * to TPageService to then adds the page behaviors.
133
	 * @param object $sender the object that raised the event
134
	 * @param mixed $param parameter of the event
135
	 */
136
	public function attachTPageServiceHandler($sender, $param)
0 ignored issues
show
Unused Code introduced by
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

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

136
	public function attachTPageServiceHandler($sender, /** @scrutinizer ignore-unused */ $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $sender is not used and could be removed. ( Ignorable by Annotation )

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

136
	public function attachTPageServiceHandler(/** @scrutinizer ignore-unused */ $sender, $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137
	{
138
		$service = $this->getService();
139
		if ($service instanceof \Prado\Web\Services\TPageService) {
140
			$service->attachEventHandler('onPreRunPage', [$this, 'attachTPageBehaviors']);
141
		}
142
	}
143
144
	/**
145
	 * This method attaches page behaviors to the TPage handling the TPageService::OnPreInitPage event.
146
	 * @param object $sender the object that raised the event
147
	 * @param \Prado\Web\UI\TPage $page the page being initialized
148
	 */
149
	public function attachTPageBehaviors($sender, $page)
150
	{
151
		foreach ($this->_pageBehaviors as $name => $properties) {
152
			$priority = $properties['priority'] ?? null;
153
			unset($properties['priority']);
154
			$page->attachBehavior($name, $properties, $priority);
155
		}
156
		$this->_pageBehaviors = [];
157
	}
158
159
	/**
160
	 * Loads behaviors and attach to the proper object. behaviors for pages are
161
	 * attached separately if and when the TPage is loaded on TPageSerivce::onPreRunPage
162
	 * @param mixed $config XML of PHP representation of the behaviors
163
	 * @throws \Prado\Exceptions\TConfigurationException if the parameter file format is invalid
164
	 */
165
	protected function loadBehaviors($config)
166
	{
167
		$isXml = false;
168
		if ($config instanceof TXmlElement) {
169
			$isXml = true;
170
			$config = $config->getElementsByTagName('behavior');
171
		} elseif (is_array($config)) {
172
			$config = $config['behaviors'];
173
		} elseif (!$config) {
174
			return;
175
		}
176
		foreach ($config as $properties) {
177
			if ($isXml) {
178
				$properties = array_change_key_case($properties->getAttributes()->toArray());
179
			} else {
180
				if (!is_array($properties)) {
181
					throw new TConfigurationException('behaviormodule_behavior_as_array_required');
182
				}
183
			}
184
			$name = $properties['name'];
185
			unset($properties['name']);
186
			if (!$name) {
187
				throw new TConfigurationException('behaviormodule_behaviorname_required');
188
			}
189
190
			$attachTo = $properties['attachto'] ?? null;
191
			$attachToClass = $properties['attachtoclass'] ?? null;
192
			unset($properties['attachto']);
193
			unset($properties['attachtoclass']);
194
			if ($attachTo === null && $attachToClass === null) {
195
				throw new TConfigurationException('behaviormodule_attachto_class_required');
196
			} elseif ($attachTo !== null && $attachToClass !== null) {
197
				throw new TConfigurationException('behaviormodule_attachto_and_class_only_one');
198
			}
199
			if ($attachToClass) {
200
				$priority = $properties['priority'] ?? null;
201
				unset($properties['priority']);
202
				$behavior = TComponent::attachClassBehavior($name, $properties, $attachToClass, $priority);
0 ignored issues
show
Bug introduced by
$properties of type array is incompatible with the type object|string expected by parameter $behavior of Prado\TComponent::attachClassBehavior(). ( Ignorable by Annotation )

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

202
				$behavior = TComponent::attachClassBehavior($name, /** @scrutinizer ignore-type */ $properties, $attachToClass, $priority);
Loading history...
203
			} else {
204
				if (strtolower($attachTo) == "page") {
205
					$this->_pageBehaviors[$name] = $properties;
206
					continue;
207
				} elseif (strncasecmp($attachTo, 'module:', 7) === 0) {
208
					$owner = $this->getApplication()->getModule(trim(substr($attachTo, 7)));
209
				} elseif (strncasecmp($attachTo, 'behavior:', 9) === 0) {
210
					$properties['name'] = $name;
211
					$this->_behaviorBehaviors[trim(substr($attachTo, 9))][] = $properties;
212
					continue;
213
				} else {
214
					$owner = $this->getSubProperty($attachTo);
215
				}
216
				$priority = $properties['priority'] ?? null;
217
				unset($properties['priority']);
218
				if (!$owner) {
219
					throw new TConfigurationException('behaviormodule_behaviorowner_required', $attachTo);
220
				}
221
				$behavior = $owner->attachBehavior($name, $properties, $priority);
222
			}
223
			if (!is_array($behavior)) {
224
				self::$_behaviors[$name] = self::$_weak ? \WeakReference::create($behavior) : $behavior;
225
			}
226
		}
227
	}
228
229
	/**
230
	 * @return array additional behaviors in a list.
231
	 */
232
	public function getAdditionalBehaviors()
233
	{
234
		return $this->_additionalBehaviors ?? [];
235
	}
236
237
	/**
238
	 * this will take a string that is an array of behaviors that has been
239
	 * through serialize(), or json array of behaviors.  If one behavior is
240
	 * set as an array, then it is automatically placed into an array.
241
	 * @param array[]|string $behaviors additional behaviors
242
	 */
243
	public function setAdditionalBehaviors($behaviors)
244
	{
245
		if (is_string($behaviors)) {
246
			if (($b = @unserialize($behaviors)) !== false) {
247
				$behaviors = $b;
248
			} elseif (($b = json_decode($behaviors, true)) !== null) {
249
				$behaviors = $b;
250
			} else {
251
				$xmldoc = new TXmlDocument('1.0', 'utf-8');
252
				$xmldoc->loadFromString($behaviors);
253
				$behaviors = $xmldoc;
254
			}
255
		}
256
		if (is_array($behaviors) && isset($behaviors['class'])) {
257
			$behaviors = [$behaviors];
258
		}
259
		if (!is_array($behaviors) && !($behaviors instanceof TXmlDocument) && $behaviors !== null) {
260
			throw new TInvalidDataTypeException('behaviormodule_additional_behaviors_invalid', $behaviors);
261
		}
262
		$this->_additionalBehaviors = $behaviors ?? [];
263
	}
264
}
265