Passed
Push — master ( e1e446...1b5c12 )
by Fabio
06:00
created

TClassBehavior::isOwner()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 6
rs 10
1
<?php
2
/**
3
 * TClassBehavior class file.
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\Collections\TWeakList;
13
use Prado\Exceptions\TInvalidOperationException;
14
use Prado\TComponent;
15
use Prado\Util\TCallChain;
16
17
/**
18
 * TClassBehavior is the base class for class-wide behaviors that extend owner components
19
 * with new stateless information, properties, run time methods, and process modification.
20
 * Each instance of the TClassBehavior can have multiple owners.  TClassBehavior
21
 * tracks all its owners.  It manages the attachment status of the behavior event
22
 * handlers on each owner.
23
 *
24
 * TClassBehavior is one of two types of behaviors in PRADO. The other type is the
25 7
 * {@link TBehavior} where each instance can have only one owner and has per object
26
 * state.
27 7
 *
28
 * TClassBehavior must attach to owner components with the same behavior name.
29
 *
30
 * Behaviors can be attached to instanced objects, with {@link TComponent::attachbehavior},
31
 * or to each class, the parent classes, interfaces, and first level traits used by
32
 * the class(es) with {@link TComponent::attachClassBehavior}. Class-wide behaviors
33 6
 * cannot be attached to the root class {@link TComponent} but can attach to any subclass.
34
 * All new components with an attached class behavior will receive the behavior on
35 6
 * instancing.  Instances of a class will receive the class behavior if they are
36
 * {@link TComponent::listen}ing.
37
 *
38
 * TClassBehavior is a subclass of {@link TBaseBehavior} that implements the core
39
 * behavior functionality.  See {@link IClassBehavior} for implementation details.
40
 *
41
 * The interface IClassBehavior is used by TComponent to inject the owner as the
42
 * first method parameter, when the behavior method is called on the owner, so the
43
 * behavior knows which owner is calling it.
44
 *
45
 * @author Brad Anderson <[email protected]>
46
 * @since 3.2.3
47
 */
48
class TClassBehavior extends TBaseBehavior implements IClassBehavior
49
{
50
	/** @var ?TWeakList The WeakReference list of owner components. Default null
51
	 */
52
	private ?TWeakList $_owners = null;
53
54
	/** @var null|array|\WeakMap This tracks whether an owner has event handlers attached. */
55
	private $_handlersInstalled;
56
57
	/**
58
	 * Cloning a new instance clears both the owners and handler attachment tracking
59
	 */
60
	public function __clone()
61
	{
62
		$this->_owners = null;
63
		$this->_handlersInstalled = null;
64
		parent::__clone();
65
	}
66
67
	/**
68
	 * Initializes the WeakMap to manager the attachment status of owners.
69
	 * Prior to PHP 8, this will use an array rather than a WeakMap.
70
	 */
71
	protected function initMap()
72
	{
73
		if (class_exists('\WeakMap')) {
74
			$this->_handlersInstalled = new \WeakMap();
75
		} else {
76
			$this->_handlersInstalled = [];
77
		}
78
	}
79
80
	/**
81
	 * Attaches the behavior object to a new owner component.  This is normally called
82
	 * by the new owner when attaching a behavior, by {@link TComponent::attachBehavior},
83
	 * and not directly called.
84
	 *
85
	 * The default implementation will add the new owner to the {@link getOwners} and
86
	 * synchronize the behavior event handlers (cached in {@link eventsLog}) with the
87
	 * new owner. Make sure you call this parent implementation if you override this
88
	 * method.
89
	 * @param TComponent $component The component this behavior is being attached to.
90
	 * @throws TInvalidOperationException When attaching to a component that is already
91
	 *   attached.  Otherwise handlers would not be unique.
92
	 * @throws TInvalidOperationException When the owner component is not a TComponent.
93
	 */
94
	public function attach($component)
95
	{
96
		if (!($component instanceof TComponent)) {
0 ignored issues
show
introduced by
$component is always a sub-type of Prado\TComponent.
Loading history...
97
			throw new TInvalidOperationException('classbehavior_bad_attach_component', $this->getName());
98
		}
99
		if (!$this->_owners) {
100
			$this->_owners = new TWeakList();
101
			$this->initMap();
102
		} elseif ($this->_owners->contains($component)) {
103
			throw new TInvalidOperationException('classbehavior_owner_attach_once', $this->getName());
104
		}
105
		$this->_owners->add($component);
106
		parent::attach($component);
107
	}
108
109
	/**
110
	 * Detaches the behavior object from an owner component.  This is normally called
111
	 * by the owner when detaching a behavior, by {@link TComponent::detachBehavior},
112
	 * and not directly called.
113
	 *
114
	 * The default implementation will remove the behavior event handlers (cached in
115
	 * {@link eventsLog}) from the owner and remove the owner from the {@link getOwners}.
116
	 * Make sure you call this parent implementation if you override this method.
117
	 * @param TComponent $component The component this behavior is being detached from.
118
	 * @throws TInvalidOperationException When detaching without an owner or from a
119
	 *   component that isn't the behaviors owner.
120
	 */
121
	public function detach($component)
122
	{
123
		if ($this->_owners === null) {
124
			throw new TInvalidOperationException('classbehavior_detach_without_owner', $this->getName());
125
		}
126
		if (!$component || !$this->_owners->contains($component)) {
0 ignored issues
show
introduced by
$component is of type Prado\TComponent, thus it always evaluated to true.
Loading history...
127
			throw new TInvalidOperationException('classbehavior_detach_wrong_owner', $this->getName());
128
		}
129
		parent::detach($component);
130
		if($this->_owners->remove($component) === 0 && !$this->_owners->getCount()) {
131
			$this->_owners = null;
132
			$this->_name = null;
133
			$this->_handlersInstalled = null;
134
		}
135
	}
136
137
	/**
138
	 * @return array The owner components that this behavior is attached to.
139
	 * @since 4.2.3
140
	 */
141
	public function getOwners(): array
142
	{
143
		if ($this->_owners) {
144
			return $this->_owners->toArray();
145
		}
146
		return [];
147
	}
148
149
	/**
150
	 * @return bool Is the behavior attached to an owner.
151
	 * @since 4.2.3
152
	 */
153
	public function hasOwner(): bool
154
	{
155
		return $this->_owners !== null;
156
	}
157
158
	/**
159
	 * @param object $component
160
	 * @return bool Is the object an owner of the behavior.
161
	 * @since 4.2.3
162
	 */
163
	public function isOwner(object $component): bool
164
	{
165
		return $this->_owners && $this->_owners->contains($component);
166
	}
167
168
	/**
169
	 * This dynamic event method tracks the behaviors enabled status of an owner. Subclasses
170
	 * can call this method (as "parent::dyEnableBehaviors()") without the $chain and it
171
	 * will delegate the $chain continuation to the subclass implementation. At this point,
172
	 * the behaviors are already enabled in the owner.
173
	 * @param TComponent $owner The owner enabling their behaviors.
174
	 * @param ?TCallChain $chain The chain of dynamic events being raised.  Default
175
	 *   is null for no continuation.
176
	 * @since 4.2.3
177
	 */
178
	public function dyEnableBehaviors($owner, ?TCallChain $chain = null)
179
	{
180
		$this->syncEventHandlers($owner);
181
		if ($chain) {
182
			$chain->dyEnableBehaviors();
183
		}
184
	}
185
186
	/**
187
	 * This dynamic event method tracks the "behaviors disabled" status of an owner. Subclasses
188
	 * can call this method (as "parent::dyDisableBehaviors()") without the $chain and
189
	 * it will delegate the $chain continuation to the subclass implementation.
190
	 * @param TComponent $owner The owner disabling their behaviors.
191
	 * @param ?TCallChain $chain The chain of dynamic events being raised.  Default
192
	 *   is null for no continuation.
193
	 * @since 4.2.3
194
	 */
195
	public function dyDisableBehaviors($owner, ?TCallChain $chain = null)
196
	{
197
		$this->syncEventHandlers($owner);
198
		if ($chain) {
199
			$chain->dyDisableBehaviors();
200
		}
201
	}
202
203
	/**
204
	 * This gets the attachment status of the behavior handlers on the given component.
205
	 * @param ?TComponent $component The component to check the handlers attachment status.
206
	 *   This parameter must be provided; null is not supported in TClassBehavior.
207
	 * @return bool Are the behavior handlers attached to the given owner events.
208
	 * @since 4.2.3
209
	 */
210
	protected function getHandlersStatus(?TComponent $component = null): ?bool
211
	{
212
		if (!$component) {
213
			return null;
214
		}
215
		$ref = is_array($this->_handlersInstalled) ? spl_object_id($component) : $component;
216
		return isset($this->_handlersInstalled[$ref]);
217
	}
218
219
	/**
220
	 * This sets the attachment status of the behavior handlers on the given owner
221
	 * component.  It only returns true when there is a change in status.
222
	 * @param TComponent $component The component to set the handlers attachment status.
223
	 * @param bool $attach "true" to attach the handlers or "false" to detach.
224
	 * @return bool Is there a change in the attachment status for the given owner
225
	 *   component.
226
	 * @since 4.2.3
227
	 */
228
	protected function setHandlersStatus(TComponent $component, bool $attach): bool
229
	{
230
		$ref = is_array($this->_handlersInstalled) ? spl_object_id($component) : $component;
231
		if($attach) {
232
			if ($this->_owners && $this->_owners->contains($component) && !isset($this->_handlersInstalled[$ref])) {
233
				$this->_handlersInstalled[$ref] = true;
234
				return true;
235
			}
236
		} elseif (isset($this->_handlersInstalled[$ref])) {
237
			unset($this->_handlersInstalled[$ref]);
238
			return true;
239
		}
240
		return false;
241
	}
242
243
	/**
244
	 * Returns an array with the names of all variables of this object that should NOT be serialized
245
	 * because their value is the default one or useless to be cached for the next page loads.
246
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
247
	 * implementation first.
248
	 * @param array $exprops by reference
249
	 * @since 4.2.3
250
	 */
251
	protected function _getZappableSleepProps(&$exprops)
252
	{
253
		parent::_getZappableSleepProps($exprops);
254
		$exprops[] = "\0" . __CLASS__ . "\0_owners";
255
		$exprops[] = "\0" . __CLASS__ . "\0_handlersInstalled";
256
	}
257
}
258