| Total Complexity | 62 |
| Total Lines | 358 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like TBaseBehavior often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TBaseBehavior, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 48 | abstract class TBaseBehavior extends TComponent implements IBaseBehavior |
||
| 49 | { |
||
| 50 | use TPriorityItemTrait; |
||
| 51 | |||
| 52 | /** @var ?string The name of the behavior in the owner[s] */ |
||
| 53 | protected ?string $_name = null; |
||
| 54 | |||
| 55 | /** @var bool Is the behavior enabled or not. Default true */ |
||
| 56 | private bool $_enabled = true; |
||
| 57 | |||
| 58 | /** @var null|bool Indicates how to maintain the event handlers. Default: false |
||
| 59 | * for default installation logic. True for always attached and null for always |
||
| 60 | * detached. |
||
| 61 | */ |
||
| 62 | private $_retainDisabledHandlers = false; |
||
| 63 | |||
| 64 | /** @var null|array|false The cached events of the behavior, for Closures. */ |
||
| 65 | private $_eventsLog = false; |
||
| 66 | |||
| 67 | /** |
||
| 68 | * Cloning a new instance clears the behavior name. |
||
| 69 | */ |
||
| 70 | public function __clone() |
||
| 71 | { |
||
| 72 | $this->_name = null; |
||
| 73 | parent::__clone(); |
||
| 74 | } |
||
| 75 | |||
| 76 | /** |
||
| 77 | * This processes behavior configuration elements. This is usually called before |
||
| 78 | * attach. This is only needed for complex behavior configurations. |
||
| 79 | * @param array|\Prado\Xml\TXmlElement $config The innards to the behavior |
||
| 80 | * configuration. |
||
| 81 | */ |
||
| 82 | public function init($config) |
||
| 83 | { |
||
| 84 | } |
||
| 85 | |||
| 86 | /** |
||
| 87 | * Declares events and the corresponding event handler methods. |
||
| 88 | * The events are defined by the {@link owner} component, while the handler |
||
| 89 | * methods defined in the behavior class. These events will be attached to the |
||
| 90 | * owner depending on the enable status and {@link getRetainDisabledHandlers}. |
||
| 91 | * The format of events is as follows: |
||
| 92 | * e.g. return ["onEvent" => function($sender, $param) {...}, "onInit" => "myInitHandler", |
||
| 93 | * 'onAnEvent' => [$this, 'methodHandler'], 'onOtherEvent' => [[$this, 'handlerMethod'], |
||
| 94 | * "behaviorMethod", function($sender, $param) {...}]; |
||
| 95 | * |
||
| 96 | * Subclasses should use {@link mergeHandlers} to combine event handlers with the |
||
| 97 | * parent's event handlers. For example: |
||
| 98 | * <code> |
||
| 99 | * return self::mergeHandlers(parent::events(), ['onEvent' => 'myHandler', ...]]); |
||
| 100 | * </code> |
||
| 101 | * |
||
| 102 | * Acceptable array values are string name of behavior method, callable, or |
||
| 103 | * an array of string behavior method names or callables. |
||
| 104 | * @return array events (array keys) and the corresponding event handler methods |
||
| 105 | * (array values). |
||
| 106 | */ |
||
| 107 | public function events() |
||
| 110 | } |
||
| 111 | |||
| 112 | /** |
||
| 113 | * Returns the cached events of the behavior where Closures are retained. TClassBehavior |
||
| 114 | * owners will all have the same Closure instances. |
||
| 115 | * @return array events (keys) and the array of corresponding event handler methods (values). |
||
| 116 | * e.g. return results look like: ['onMyEvent' => ['objectMethod', $callable, $closure], |
||
| 117 | * 'onEvent' => ['behaviorMethod', $callable2, $closure2]] |
||
| 118 | */ |
||
| 119 | public function eventsLog() |
||
| 120 | { |
||
| 121 | if ($this->_eventsLog === false) { |
||
| 122 | $this->_eventsLog = self::mergeHandlers($this->events()); |
||
| 123 | } |
||
| 124 | return $this->_eventsLog; |
||
|
|
|||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * By default, all events with handlers in {@link eventsLog} are attached or there |
||
| 129 | * will be an error. When this is false, attaching behavior event handlers will |
||
| 130 | * not fail if the owner is missing events and handlers are not attached. Put |
||
| 131 | * another way, when false, behavior event handlers are optional rather than made |
||
| 132 | * mandatory. |
||
| 133 | * @return bool Strictly enforce attaching behavior event handlers. Default true. |
||
| 134 | */ |
||
| 135 | public function getStrictEvents(): bool |
||
| 136 | { |
||
| 137 | return true; |
||
| 138 | } |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Merges the handlers of an event into an array of keys (as event names) and values |
||
| 142 | * as an array of behavior method names (strings) and callable. |
||
| 143 | * @param array $args The array of events (keys) and values of either behavior method |
||
| 144 | * names (strings), callable, or an array of the former. Multiple handlers can be |
||
| 145 | * set on a single event to allow subclasses to have their own handlers. |
||
| 146 | * @return array The array of combined events (keys) and an array (values) of handlers |
||
| 147 | * for each. |
||
| 148 | */ |
||
| 149 | public static function mergeHandlers(...$args): array |
||
| 150 | { |
||
| 151 | if(empty($args)) { |
||
| 152 | return []; |
||
| 153 | } |
||
| 154 | $combined = []; |
||
| 155 | foreach ($args as $events) { |
||
| 156 | foreach ($events as $name => $handler) { |
||
| 157 | if (!isset($combined[$name])) { |
||
| 158 | $combined[$name] = []; |
||
| 159 | } |
||
| 160 | if (is_string($handler) || is_callable($handler)) { |
||
| 161 | $combined[$name][] = $handler; |
||
| 162 | } elseif (is_array($handler)) { |
||
| 163 | $combined[$name] += $handler; |
||
| 164 | } |
||
| 165 | } |
||
| 166 | } |
||
| 167 | return $combined; |
||
| 168 | } |
||
| 169 | |||
| 170 | /** |
||
| 171 | * Attaches the behavior object to a new owner component. |
||
| 172 | * The default implementation will synchronize attachment of event handlers declared |
||
| 173 | * in {@link eventsLog}. |
||
| 174 | * Make sure you call the parent implementation if you override this method. |
||
| 175 | * @param TComponent $component the component that this behavior is being attached to. |
||
| 176 | */ |
||
| 177 | public function attach($component) |
||
| 178 | { |
||
| 179 | $this->syncEventHandlers($component); |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * Detaches the behavior object from an owner component. |
||
| 184 | * The default implementation will detach event handlers declared in {@link eventsLog}. |
||
| 185 | * Make sure you call this parent implementation if you override this method. |
||
| 186 | * @param TComponent $component the component that this behavior is being detached from. |
||
| 187 | */ |
||
| 188 | public function detach($component) |
||
| 189 | { |
||
| 190 | $this->detachEventHandlers($component); |
||
| 191 | } |
||
| 192 | |||
| 193 | /** |
||
| 194 | * @return ?string The name of the behavior in the owner[s]. |
||
| 195 | */ |
||
| 196 | public function getName(): ?string |
||
| 197 | { |
||
| 198 | return $this->_name; |
||
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * @param ?string $value The name of the behavior in the owner[s]. |
||
| 203 | * @throws TInvalidOperationException When there is an owner and the new name is |
||
| 204 | * not the same as the given name. |
||
| 205 | */ |
||
| 206 | public function setName($value) |
||
| 207 | { |
||
| 208 | if (!$this->hasOwner()) { |
||
| 209 | $this->_name = TPropertyValue::ensureString($value); |
||
| 210 | } elseif ($value !== $this->_name) { |
||
| 211 | throw new TInvalidOperationException('basebehavior_cannot_setname_with_owner', $this->_name, $value); |
||
| 212 | } |
||
| 213 | } |
||
| 214 | |||
| 215 | /** |
||
| 216 | * @return bool Whether this behavior is enabled, Default true. |
||
| 217 | */ |
||
| 218 | public function getEnabled(): bool |
||
| 219 | { |
||
| 220 | return $this->_enabled; |
||
| 221 | } |
||
| 222 | |||
| 223 | /** |
||
| 224 | * This method sets the enabled flag and synchronizes the behavior's handlers with |
||
| 225 | * its owner[s]. |
||
| 226 | * @param bool|string $value Whether this behavior is enabled. |
||
| 227 | */ |
||
| 228 | public function setEnabled($value) |
||
| 234 | } |
||
| 235 | } |
||
| 236 | |||
| 237 | /** |
||
| 238 | * RetainDisabledHandlers has three states: |
||
| 239 | * 1) "true" is to always install the event handlers regardless of behavior enabled |
||
| 240 | * or owner behaviors enabled status. |
||
| 241 | * 2) "false" is the default attachment logic to remove the event handlers when |
||
| 242 | * the behavior or owner behaviors are disabled. (default) |
||
| 243 | * 3) "null" is to always remove the event handlers. |
||
| 244 | * @return null|bool Does the behavior retain the handlers when disabled (true), |
||
| 245 | * remove the handlers when the behavior/owner is disabled (false), or always |
||
| 246 | * remove the handlers (null). |
||
| 247 | */ |
||
| 248 | public function getRetainDisabledHandlers() |
||
| 249 | { |
||
| 250 | return $this->_retainDisabledHandlers; |
||
| 251 | } |
||
| 252 | |||
| 253 | /** |
||
| 254 | * This changes the retaining of disabled behavior handlers and then synchronizes |
||
| 255 | * the behavior's handlers with its owner[s]. There are three acceptable values: |
||
| 256 | * 1) "true" is to always install the event handlers regardless of behavior enabled |
||
| 257 | * or owner behaviors enabled status. |
||
| 258 | * 2) "false" is the default attachment logic to remove the event handlers when |
||
| 259 | * the behavior or owner behaviors are disabled. (default) |
||
| 260 | * 3) "null" is to always remove the event handlers. |
||
| 261 | * @param null|bool $value Does the behavior retain the handlers when disabled (true), |
||
| 262 | * remove the handlers when the behavior/owner is disabled (false), or always |
||
| 263 | * remove handlers (null). |
||
| 264 | */ |
||
| 265 | public function setRetainDisabledHandlers($value) |
||
| 266 | { |
||
| 267 | if ($value !== 0 && $value !== null && (!is_string($value) || strtolower($value) !== 'null' && $value !== '0')) { |
||
| 268 | $value = TPropertyValue::ensureBoolean($value); |
||
| 269 | } else { |
||
| 270 | $value = null; |
||
| 271 | } |
||
| 272 | if ($this->_retainDisabledHandlers !== $value) { |
||
| 273 | $this->_retainDisabledHandlers = $value; |
||
| 274 | $this->syncEventHandlers(); |
||
| 275 | } |
||
| 276 | } |
||
| 277 | |||
| 278 | /** |
||
| 279 | * This synchronizes an owner's events of the behavior event handlers by attaching |
||
| 280 | * or detaching where needed. A behaviors handlers are attached depending on whether |
||
| 281 | * {@link getRetainDisabledHandlers} is true (or null) or both the owner and behavior are |
||
| 282 | * [Behavior] enabled. The $attachOverride will set RetainDisabledHandlers when not |
||
| 283 | * its default value 0 and thus can act like {@link setRetainDisabledHandlers}. |
||
| 284 | * @param ?object $component The component to manage the behaviors handlers on. Default |
||
| 285 | * is null for synchronizing all owners. |
||
| 286 | * @param null|bool|int $attachOverride Overrides the default attachment logic or whether |
||
| 287 | * to install and forcibly attach or detach the handlers when true or null. false resets |
||
| 288 | * RetainDisabledHandlers to the default attachment logic (of when enabled). Default is 0 |
||
| 289 | * for normal action and no override (true/null) or reset (false). |
||
| 290 | * @throws TInvalidOperationException When synchronizing a component without owners |
||
| 291 | * or the component that isn't an owner. |
||
| 292 | * @since 4.2.3 |
||
| 293 | */ |
||
| 294 | public function syncEventHandlers(?object $component = null, $attachOverride = 0) |
||
| 295 | { |
||
| 296 | $hasOwner = $this->hasOwner(); |
||
| 297 | if ($component && !$hasOwner) { |
||
| 298 | throw new TInvalidOperationException('basebehavior_sync_no_owner', $this->getName()); |
||
| 299 | } |
||
| 300 | if (!$hasOwner) { |
||
| 301 | return; |
||
| 302 | } |
||
| 303 | if ($component && !$this->isOwner($component)) { |
||
| 304 | throw new TInvalidOperationException('basebehavior_sync_not_owner', $this->getName()); |
||
| 305 | } |
||
| 306 | foreach ($component ? [$component] : $this->getOwners() as $component) { |
||
| 307 | if (is_bool($attachOverride) || $attachOverride === null) { |
||
| 308 | $this->_retainDisabledHandlers = $attachOverride; |
||
| 309 | } |
||
| 310 | if ($this->_retainDisabledHandlers !== false) { |
||
| 311 | $install = (bool) $this->_retainDisabledHandlers; |
||
| 312 | } else { |
||
| 313 | $install = $this->getEnabled() && $component->getBehaviorsEnabled(); |
||
| 314 | } |
||
| 315 | if ($this->getHandlersStatus($component) ^ $install) { |
||
| 316 | if ($install) { |
||
| 317 | $this->attachEventHandlers($component); |
||
| 318 | } else { |
||
| 319 | $this->detachEventHandlers($component); |
||
| 320 | } |
||
| 321 | } |
||
| 322 | } |
||
| 323 | } |
||
| 324 | |||
| 325 | /* |
||
| 326 | * Attaches the behavior event handlers to an owner component. This tracks of the |
||
| 327 | * attachment status of handlers on the owner components. |
||
| 328 | * @param TComponent $component The component to attach the behavior event handlers to. |
||
| 329 | * @since 4.2.3 |
||
| 330 | */ |
||
| 331 | protected function attachEventHandlers(TComponent $component): void |
||
| 332 | { |
||
| 333 | if ($this->setHandlersStatus($component, true)) { |
||
| 334 | $priority = $this->getPriority(); |
||
| 335 | $strict = $this->getStrictEvents(); |
||
| 336 | foreach ($this->eventsLog() as $event => $handlers) { |
||
| 337 | if ($strict || $this->hasEvent($event)) { |
||
| 338 | foreach($handlers as $handler) { |
||
| 339 | $component->attachEventHandler($event, is_string($handler) ? [$this, $handler] : $handler, $priority); |
||
| 340 | } |
||
| 341 | } |
||
| 342 | } |
||
| 343 | } |
||
| 344 | } |
||
| 345 | |||
| 346 | /* |
||
| 347 | * Detaches the behavior event handlers from an owner component. This tracks of the |
||
| 348 | * attachment status of handlers on the owner components. |
||
| 349 | * @param TComponent $component The component to detach the behavior event handlers from. |
||
| 350 | * @since 4.2.3 |
||
| 351 | */ |
||
| 352 | protected function detachEventHandlers(TComponent $component): void |
||
| 353 | { |
||
| 354 | if ($this->setHandlersStatus($component, false)) { |
||
| 355 | $strict = $this->getStrictEvents(); |
||
| 356 | foreach ($this->eventsLog() as $event => $handlers) { |
||
| 357 | if ($strict || $this->hasEvent($event)) { |
||
| 358 | foreach($handlers as $handler) { |
||
| 359 | $component->detachEventHandler($event, is_string($handler) ? [$this, $handler] : $handler); |
||
| 360 | } |
||
| 361 | } |
||
| 362 | } |
||
| 363 | } |
||
| 364 | } |
||
| 365 | |||
| 366 | /** |
||
| 367 | * This gets the attachment status of the behavior event handlers on the given |
||
| 368 | * component. |
||
| 369 | * @param ?TComponent $component The component to check the status of the handlers. |
||
| 370 | * Null only works for IBehavior and returns the status of the single owner. |
||
| 371 | * @return bool Are the behavior handlers attached to the given owner events. |
||
| 372 | * @since 4.2.3 |
||
| 373 | */ |
||
| 374 | abstract protected function getHandlersStatus(?TComponent $component = null): ?bool; |
||
| 375 | |||
| 376 | /** |
||
| 377 | * This sets the attachment status of the behavior event handlers on the given |
||
| 378 | * component. |
||
| 379 | * @param TComponent $component The component to set the status of. |
||
| 380 | * @param bool $attach "true" to attach the handlers or "false" detach them. |
||
| 381 | * @return bool Is there a change in the attachment status for the given owner |
||
| 382 | * component. |
||
| 383 | * @since 4.2.3 |
||
| 384 | */ |
||
| 385 | abstract protected function setHandlersStatus(TComponent $component, bool $attach): bool; |
||
| 386 | |||
| 387 | /** |
||
| 388 | * Returns an array with the names of all variables of this object that should NOT be serialized |
||
| 389 | * because their value is the default one or useless to be cached for the next page loads. |
||
| 390 | * Reimplement in derived classes to add new variables, but remember to also to call the parent |
||
| 391 | * implementation first. |
||
| 392 | * @param array &$exprops Properties of the object to exclude. |
||
| 393 | */ |
||
| 394 | protected function _getZappableSleepProps(&$exprops) |
||
| 406 | } |
||
| 407 | } |
||
| 408 |