From 037f98f6066d0787b54bb08470deb34e40070dbe Mon Sep 17 00:00:00 2001 From: Brad Kent Date: Mon, 17 Jul 2023 09:49:42 -0500 Subject: [PATCH] Update PubSub to 3.0 css: add a couple more resets Discord, Slack, & Teams routes: assert necessary config new `Utility/Php::getDebugType` method --- composer.json | 4 +- src/CurlHttpMessage/Handler/Mock.php | 6 +- src/CurlHttpMessage/HandlerStack.php | 18 +- src/Debug/Framework/Yii1_1/ErrorLogger.php | 3 +- src/Debug/Framework/Yii2/Module.php | 3 +- src/Debug/Method/Helper.php | 4 +- src/Debug/Plugin/CustomMethod/General.php | 2 + src/Debug/Plugin/CustomMethod/ReqRes.php | 2 +- src/Debug/Plugin/Manager.php | 10 +- src/Debug/Route/Discord.php | 76 ++-- src/Debug/Route/Slack.php | 83 ++-- src/Debug/Route/Teams.php | 102 +++-- src/Debug/Utility/ErrorLevel.php | 2 +- src/Debug/Utility/Php.php | 82 +++- src/Debug/Utility/StringUtil.php | 7 +- src/Debug/css/Debug.css | 2 +- src/Debug/scss/_base.scss | 1 + src/Debug/scss/_tabs.scss | 1 + src/ErrorHandler/AbstractErrorHandler.php | 8 +- src/ErrorHandler/ErrorHandler.php | 4 +- src/HttpMessage/AbstractServerRequest.php | 2 +- src/HttpMessage/AbstractStream.php | 6 +- src/HttpMessage/AssertionTrait.php | 18 +- src/PubSub/Event.php | 4 +- src/PubSub/Manager.php | 230 +++-------- src/PubSub/ManagerHelperTrait.php | 363 ++++++++++++++++++ src/PubSub/ValueStore.php | 3 +- src/Teams/CardUtilityTrait.php | 12 +- src/Teams/Elements/FactSet.php | 2 +- src/Teams/Elements/RichTextBlock.php | 2 +- src/Teams/Elements/Table.php | 2 +- src/Teams/Elements/TableCell.php | 4 +- src/Teams/Elements/TableRow.php | 2 +- src/Teams/Elements/TextBlock.php | 2 +- tests/CurlHttpMessage/Handler/MockTest.php | 2 +- tests/Debug/ConfigTest.php | 6 +- tests/Debug/DebugTest.php | 19 +- tests/Debug/DebugTestFramework.php | 14 +- tests/Debug/Method/PluginMethodReqResTest.php | 2 +- tests/Debug/Method/TraceTest.php | 2 +- tests/Debug/Utility/PhpTest.php | 48 +++ tests/Debug/Utility/StringUtilTest.php | 4 +- 42 files changed, 811 insertions(+), 358 deletions(-) create mode 100644 src/PubSub/ManagerHelperTrait.php diff --git a/composer.json b/composer.json index 80aa20c2..96601718 100644 --- a/composer.json +++ b/composer.json @@ -49,10 +49,10 @@ "replace": { "bdk/backtrace": "2.1.1", "bdk/curl-http-message": "1.0", - "bdk/errorhandler": "3.2", + "bdk/errorhandler": "3.3", "bdk/http-message": "1.0", "bdk/promise": "1.0", - "bdk/pubsub": "2.4", + "bdk/pubsub": "3.0", "bdk/slack": "1.0", "bdk/teams": "1.0", "psr/http-message": "1.0.1" diff --git a/src/CurlHttpMessage/Handler/Mock.php b/src/CurlHttpMessage/Handler/Mock.php index cf558daf..77d2860e 100644 --- a/src/CurlHttpMessage/Handler/Mock.php +++ b/src/CurlHttpMessage/Handler/Mock.php @@ -123,8 +123,10 @@ public function append($values) || $value instanceof Exception || \is_callable($value); if ($isValid === false) { - $type = \is_object($value) ? \get_class($value) : \gettype($value); - throw new InvalidArgumentException('Expected a Response, Promise, Throwable or callable. ' . $type . ' provided'); + throw new InvalidArgumentException(\sprintf( + 'Expected a Response, Promise, Throwable or callable. %s provided', + \is_object($value) ? \get_class($value) : \gettype($value) + )); } $this->queue[] = $value; } diff --git a/src/CurlHttpMessage/HandlerStack.php b/src/CurlHttpMessage/HandlerStack.php index 8c5c2e1e..d980fe50 100644 --- a/src/CurlHttpMessage/HandlerStack.php +++ b/src/CurlHttpMessage/HandlerStack.php @@ -121,7 +121,7 @@ public function remove($remove) if (\is_string($remove) === false && \is_callable($remove) === false) { throw new InvalidArgumentException(\sprintf( __METHOD__ . ' requires a string or callable. %s provided', - \gettype($remove) + self::getDebugType($remove) )); } $index = \is_callable($remove) ? 0 : 1; @@ -172,7 +172,7 @@ private function assertName($name) if (\is_string($name) === false) { throw new InvalidArgumentException(\sprintf( 'Name should be a string. %s provided', - \gettype($name) + self::getDebugType($name) )); } $found = \array_filter($this->stack, static function ($callableAndName) use ($name) { @@ -202,6 +202,20 @@ private function findByName($name) throw new RuntimeException('Middleware not found: ' . $name); } + /** + * Gets the type name of a variable in a way that is suitable for debugging + * + * @param mixed $value The value being type checked + * + * @return string + */ + protected static function getDebugType($value) + { + return \is_object($value) + ? \get_class($value) + : \gettype($value); + } + /** * Splices a function into the middleware list at a specific position. * diff --git a/src/Debug/Framework/Yii1_1/ErrorLogger.php b/src/Debug/Framework/Yii1_1/ErrorLogger.php index cc9be593..268425cc 100644 --- a/src/Debug/Framework/Yii1_1/ErrorLogger.php +++ b/src/Debug/Framework/Yii1_1/ErrorLogger.php @@ -194,7 +194,8 @@ private function logIgnoredErrors() private function republishShutdown() { $eventManager = $this->debug->eventManager; - foreach ($eventManager->getSubscribers(EventManager::EVENT_PHP_SHUTDOWN) as $callable) { + foreach ($eventManager->getSubscribers(EventManager::EVENT_PHP_SHUTDOWN) as $subscriberInfo) { + $callable = $subscriberInfo['callable']; $eventManager->unsubscribe(EventManager::EVENT_PHP_SHUTDOWN, $callable); if (\is_array($callable) && $callable[0] === $this->debug->errorHandler) { break; diff --git a/src/Debug/Framework/Yii2/Module.php b/src/Debug/Framework/Yii2/Module.php index 2a401f71..768d54b9 100644 --- a/src/Debug/Framework/Yii2/Module.php +++ b/src/Debug/Framework/Yii2/Module.php @@ -233,7 +233,8 @@ public function onErrorLow(Error $error) // exit within shutdown procedure (that's us) = immediate exit // so... unsubscribe the callables that have already been called and // re-publish the shutdown event before calling yii's error handler - foreach ($this->debug->rootInstance->eventManager->getSubscribers(EventManager::EVENT_PHP_SHUTDOWN) as $callable) { + foreach ($this->debug->rootInstance->eventManager->getSubscribers(EventManager::EVENT_PHP_SHUTDOWN) as $subscriberInfo) { + $callable = $subscriberInfo['callable']; $this->debug->rootInstance->eventManager->unsubscribe(EventManager::EVENT_PHP_SHUTDOWN, $callable); if (\is_array($callable) && $callable[0] === $this->debug->rootInstance->errorHandler) { break; diff --git a/src/Debug/Method/Helper.php b/src/Debug/Method/Helper.php index 605886e3..5d4115f7 100644 --- a/src/Debug/Method/Helper.php +++ b/src/Debug/Method/Helper.php @@ -125,9 +125,7 @@ public function doTrace(LogEntry $logEntry) if (\is_string($caption) === false) { $this->debug->warn(\sprintf( 'trace caption should be a string. %s provided', - \is_object($caption) - ? \get_class($caption) - : \gettype($caption) + $this->debug->php->getDebugType($caption) )); $logEntry->setMeta('caption', 'trace'); } diff --git a/src/Debug/Plugin/CustomMethod/General.php b/src/Debug/Plugin/CustomMethod/General.php index 1f6ef1cf..c8b1a5fe 100644 --- a/src/Debug/Plugin/CustomMethod/General.php +++ b/src/Debug/Plugin/CustomMethod/General.php @@ -238,6 +238,8 @@ public function setErrorCaller($caller = null) /** * Dump values to output * + * Similar to php's `var_dump()`. Dump values immediately + * * @param mixed $arg,... message / values * * @return void diff --git a/src/Debug/Plugin/CustomMethod/ReqRes.php b/src/Debug/Plugin/CustomMethod/ReqRes.php index 9246e954..97317f1f 100644 --- a/src/Debug/Plugin/CustomMethod/ReqRes.php +++ b/src/Debug/Plugin/CustomMethod/ReqRes.php @@ -267,7 +267,7 @@ public function writeToResponse($response) } throw new InvalidArgumentException(\sprintf( 'writeToResponse expects ResponseInterface or HttpFoundationResponse, but %s provided', - \is_object($response) ? \get_class($response) : \gettype($response) + $this->debug->php->getDebugType($response) )); } diff --git a/src/Debug/Plugin/Manager.php b/src/Debug/Plugin/Manager.php index 4aed5b03..2a150b25 100644 --- a/src/Debug/Plugin/Manager.php +++ b/src/Debug/Plugin/Manager.php @@ -157,9 +157,11 @@ private function assertPlugin($plugin) return; } $backtrace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); - $type = \is_object($plugin) - ? \get_class($plugin) - : \gettype($plugin); - throw new InvalidArgumentException($backtrace[1]['function'] . ' expects \\bdk\\Debug\\AssetProviderInterface and/or \\bdk\\PubSub\\SubscriberInterface. ' . $type . ' provided'); + throw new InvalidArgumentException(\sprintf( + '%s expects %s. %s provided', + $backtrace[1]['function'], + '\\bdk\\Debug\\AssetProviderInterface and/or \\bdk\\PubSub\\SubscriberInterface', + $this->debug->php->getDebugType($plugin) + )); } } diff --git a/src/Debug/Route/Discord.php b/src/Debug/Route/Discord.php index ae3199ca..3ac378f6 100644 --- a/src/Debug/Route/Discord.php +++ b/src/Debug/Route/Discord.php @@ -16,6 +16,7 @@ use bdk\Debug; use bdk\ErrorHandler; use bdk\ErrorHandler\Error; +use RuntimeException; /** * Send critical errors to Discord @@ -80,39 +81,23 @@ public function onError(Error $error) } /** - * Send message to Discord - * - * @param array $message Discord message + * Validate configuration values * * @return void - */ - protected function sendMessage(array $message) - { - $client = $this->getClient(); - $client->post( - $this->cfg['webhookUrl'], - array( - 'Content-Type' => 'application/json; charset=utf-8', - ), - $message - ); - } - - /** - * Return CurlHttpMessage * - * @return CurlHttpMessageClient + * @throws RuntimeException */ - protected function getClient() + private function assertCfg() { - if ($this->client) { - return $this->client; - } - $this->client = new CurlHttpMessageClient(); - if (\is_callable($this->cfg['onClientInit'])) { - \call_user_func($this->cfg['onClientInit'], $this->client); + if ($this->cfg['webhookUrl']) { + return; } - return $this->client; + throw new RuntimeException(\sprintf( + '%s: missing config value: %s. Also tried env-var: %s', + __CLASS__, + 'webhookUrl', + 'DISCORD_WEBHOOK_URL' + )); } /** @@ -138,4 +123,41 @@ private function buildMessage(Error $error) . $error['file'] . ' (line ' . $error['line'] . ')', ); } + + /** + * Return CurlHttpMessage + * + * @return CurlHttpMessageClient + */ + protected function getClient() + { + if ($this->client) { + return $this->client; + } + $this->assertCfg(); + $this->client = new CurlHttpMessageClient(); + if (\is_callable($this->cfg['onClientInit'])) { + \call_user_func($this->cfg['onClientInit'], $this->client); + } + return $this->client; + } + + /** + * Send message to Discord + * + * @param array $message Discord message + * + * @return void + */ + protected function sendMessage(array $message) + { + $client = $this->getClient(); + $client->post( + $this->cfg['webhookUrl'], + array( + 'Content-Type' => 'application/json; charset=utf-8', + ), + $message + ); + } } diff --git a/src/Debug/Route/Slack.php b/src/Debug/Route/Slack.php index dc297f90..43d597c4 100644 --- a/src/Debug/Route/Slack.php +++ b/src/Debug/Route/Slack.php @@ -18,6 +18,7 @@ use bdk\Slack\SlackApi; use bdk\Slack\SlackMessage; use bdk\Slack\SlackWebhook; +use RuntimeException; /** * Send critical errors to Slack @@ -85,37 +86,32 @@ public function onError(Error $error) } /** - * Send message(s) to Slack - * - * @param SlackMessage[] $messages Slack messages + * Validate configuration values * * @return void - */ - protected function sendMessages(array $messages) - { - $ts = null; - foreach ($messages as $message) { - $message = $message->withValue('thread_ts', $ts); - $response = $this->sendMessage($message); - $ts = $ts ?: $response['ts']; - } - } - - /** - * Send message to Slack * - * @param SlackMessage $message Slack messages - * - * @return array + * @throws RuntimeException */ - protected function sendMessage(SlackMessage $message) + private function assertCfg() { - $slackClient = $this->getSlackClient(); - if ($slackClient instanceof SlackApi) { - $message = $message->withChannel($this->cfg['channel']); - return $slackClient->chatPostMessage($message); + if (\in_array($this->cfg['use'], array('auto', 'api', 'webhook'), true) === false) { + throw new RuntimeException(\sprintf( + '%s: Invalid cfg value. `use` must be one of "auto", "api", or "webhook"', + __CLASS__ + )); } - return $slackClient->post($message); + if ($this->cfg['token'] && $this->cfg['channel']) { + return; + } + if ($this->cfg['webhookUrl']) { + return; + } + throw new RuntimeException(\sprintf( + '%s: missing config value(s). Must configure %s. Or define equivalent environment variable(s) (%s)', + __CLASS__, + 'token+channel or webhookUrl', + 'SLACK_TOKEN, SLACK_CHANNEL, SLACK_WEBHOOK_URL' + )); } /** @@ -123,11 +119,12 @@ protected function sendMessage(SlackMessage $message) * * @return SlackApi|SlackWebhook */ - protected function getSlackClient() + protected function getClient() { if ($this->slackClient) { return $this->slackClient; } + $this->assertCfg(); $use = $this->cfg['use']; if ($use === 'auto') { $use = $this->cfg['token'] && $this->cfg['channel'] @@ -203,4 +200,38 @@ private function buildMessageBacktrace(Error $error) ->withHeader('Backtrace') ->withSection(\implode("\n", $frames)); } + + /** + * Send message(s) to Slack + * + * @param SlackMessage[] $messages Slack messages + * + * @return void + */ + protected function sendMessages(array $messages) + { + $ts = null; + foreach ($messages as $message) { + $message = $message->withValue('thread_ts', $ts); + $response = $this->sendMessage($message); + $ts = $ts ?: $response['ts']; + } + } + + /** + * Send message to Slack + * + * @param SlackMessage $message Slack messages + * + * @return array + */ + protected function sendMessage(SlackMessage $message) + { + $slackClient = $this->getClient(); + if ($slackClient instanceof SlackApi) { + $message = $message->withChannel($this->cfg['channel']); + return $slackClient->chatPostMessage($message); + } + return $slackClient->post($message); + } } diff --git a/src/Debug/Route/Teams.php b/src/Debug/Route/Teams.php index 1ac8de1e..5eea8f65 100644 --- a/src/Debug/Route/Teams.php +++ b/src/Debug/Route/Teams.php @@ -23,6 +23,7 @@ use bdk\Teams\Elements\TextBlock as TeamsTextBlock; use bdk\Teams\Enums; use bdk\Teams\TeamsWebhook; +use RuntimeException; /** * Send critical errors to Teams @@ -85,33 +86,54 @@ public function onError(Error $error) } /** - * Return SlackApi or SlackWebhook client depending on what config provided + * Validate configuration values * - * @return SlackApi|SlackWebhook + * @return void + * + * @throws RuntimeException */ - protected function getTeamsClient() + private function assertCfg() { - if ($this->teamsClient) { - return $this->teamsClient; - } - $this->teamsClient = new TeamsWebhook($this->cfg['webhookUrl']); - if (\is_callable($this->cfg['onClientInit'])) { - \call_user_func($this->cfg['onClientInit'], $this->teamsClient); + if ($this->cfg['webhookUrl']) { + return; } - return $this->teamsClient; + throw new RuntimeException(\sprintf( + '%s: missing config value: %s. Also tried env-var: %s', + __CLASS__, + 'webhookUrl', + 'TEAMS_WEBHOOK_URL' + )); } /** - * Send message to Teams + * Build Teams Table element with backtrace info * - * @param CardInterface $card Card message + * @param Error $error Error instance * - * @return array + * @return TeamsTableRow */ - protected function sendMessage(CardInterface $card) + private function buildBacktraceTable(Error $error) { - $teamsClient = $this->getTeamsClient(); - return $teamsClient->post($card); + $rows = array( + (new TeamsTableRow(array('file', 'line', 'function'))) + ->withStyle(Enums::CONTAINER_STYLE_ATTENTION), + ); + $frameDefault = array('file' => null, 'line' => null, 'function' => null); + foreach ($error['backtrace'] as $frame) { + $frame = \array_merge($frameDefault, $frame); + $frame = \array_intersect_key($frame, $frameDefault); + $rows[] = $frame; + } + return (new TeamsTable()) + ->withColumns(array( + array('width' => 2), + array('width' => 0.5, 'horizontalAlignment' => Enums::HORIZONTAL_ALIGNMENT_RIGHT), + array('width' => 1), + )) + ->withFirstRowAsHeader() + ->withGridStyle(Enums::CONTAINER_STYLE_ATTENTION) + ->withShowGridLines() + ->withRows($rows); } /** @@ -158,33 +180,33 @@ private function buildMessage(Error $error) } /** - * Build Teams Table element with backtrace info - * - * @param Error $error Error instance + * Return SlackApi or SlackWebhook client depending on what config provided * - * @return TeamsTableRow + * @return SlackApi|SlackWebhook */ - private function buildBacktraceTable(Error $error) + protected function getClient() { - $rows = array( - (new TeamsTableRow(array('file', 'line', 'function'))) - ->withStyle(Enums::CONTAINER_STYLE_ATTENTION), - ); - $frameDefault = array('file' => null, 'line' => null, 'function' => null); - foreach ($error['backtrace'] as $frame) { - $frame = \array_merge($frameDefault, $frame); - $frame = \array_intersect_key($frame, $frameDefault); - $rows[] = $frame; + if ($this->teamsClient) { + return $this->teamsClient; } - return (new TeamsTable()) - ->withColumns(array( - array('width' => 2), - array('width' => 0.5, 'horizontalAlignment' => Enums::HORIZONTAL_ALIGNMENT_RIGHT), - array('width' => 1), - )) - ->withFirstRowAsHeader() - ->withGridStyle(Enums::CONTAINER_STYLE_ATTENTION) - ->withShowGridLines() - ->withRows($rows); + $this->assertCfg(); + $this->teamsClient = new TeamsWebhook($this->cfg['webhookUrl']); + if (\is_callable($this->cfg['onClientInit'])) { + \call_user_func($this->cfg['onClientInit'], $this->teamsClient); + } + return $this->teamsClient; + } + + /** + * Send message to Teams + * + * @param CardInterface $card Card message + * + * @return array + */ + protected function sendMessage(CardInterface $card) + { + $teamsClient = $this->getClient(); + return $teamsClient->post($card); } } diff --git a/src/Debug/Utility/ErrorLevel.php b/src/Debug/Utility/ErrorLevel.php index 5e60f79e..659633eb 100644 --- a/src/Debug/Utility/ErrorLevel.php +++ b/src/Debug/Utility/ErrorLevel.php @@ -100,7 +100,7 @@ public static function toConstantString($errorReportingLevel = null, $phpVer = n private static function calculateEall($constants, $phpVer) { $eAll = \array_sum($constants); - if (isset($constants['E_STRICT']) && \version_compare($phpVer, '5.4.0', '<')) { + if (isset($constants['E_STRICT']) && PHP_VERSION_ID < 50400) { // E_STRICT not included in E_ALL until 5.4 $eAll -= $constants['E_STRICT']; } diff --git a/src/Debug/Utility/Php.php b/src/Debug/Utility/Php.php index 3bf56f9a..8bf246e5 100644 --- a/src/Debug/Utility/Php.php +++ b/src/Debug/Utility/Php.php @@ -47,6 +47,43 @@ public static function friendlyClassName($mixed) : $reflector->getName(); } + /** + * Gets the type name of a variable in a way that is suitable for debugging + * + * @param mixed $val The value being type checked + * + * @return string + * + * @see https://github.com/symfony/polyfill/blob/main/src/Php80/Php80.php + */ + public static function getDebugType($val) + { + if (PHP_VERSION_ID >= 80000) { + return \get_debug_type($val); + } + + switch (true) { + case $val === null: + return 'null'; + case \is_bool($val): + return 'bool'; + case \is_string($val): + return 'string'; + case \is_array($val): + return 'array'; + case \is_int($val): + return 'int'; + case \is_float($val): + return 'float'; + case \is_object($val): + return self::getDebugTypeObject($val); + case $val instanceof \__PHP_Incomplete_Class: + return '__PHP_Incomplete_Class'; + default: + return self::getDebugTypeResource($val); + } + } + /** * returns required/included files sorted by directory * @@ -164,6 +201,48 @@ public static function unserializeSafe($serialized, $allowedClasses = array()) return \unserialize($serialized); } + /** + * get friendly class name + * + * @param [type] $obj [description] + * + * @return string + */ + private static function getDebugTypeObject($obj) + { + $class = \get_class($obj); + if (\strpos($class, '@') === false) { + return $class; + } + $class = \get_parent_class($class) ?: \key(\class_implements($class)) ?: 'class'; + return $class . '@anonymous'; + } + + /** + * Get resource type + * + * @param mixed $val Resource + * + * @return string + */ + private static function getDebugTypeResource($val) + { + // @phpcs:ignore Squiz.WhiteSpace.ScopeClosingBrace + \set_error_handler(static function () {}); + $type = \get_resource_type($val); + \restore_error_handler(); + + if ($type === null) { + // closed resource (php < 7.2) + $type = 'closed'; + } + if ($type === 'Unknown') { + $type = 'closed'; + } + + return 'resource (' . $type . ')'; + } + /** * Test if array is a callable * @@ -254,7 +333,8 @@ private static function friendlyClassNameAnon(Reflector $reflector) $extends = $parentClassRef ? $parentClassRef->getName() : null; - return ($extends ?: \current($reflector->getInterfaceNames()) ?: 'class') . '@anonymous'; + $class = $extends ?: \current($reflector->getInterfaceNames()) ?: 'class'; + return $class . '@anonymous'; } /** diff --git a/src/Debug/Utility/StringUtil.php b/src/Debug/Utility/StringUtil.php index 1a215301..aa70c8ed 100644 --- a/src/Debug/Utility/StringUtil.php +++ b/src/Debug/Utility/StringUtil.php @@ -13,6 +13,7 @@ namespace bdk\Debug\Utility; use bdk\Debug\Utility\ArrayUtil; +use bdk\Debug\Utility\Php; use DOMDocument; use InvalidArgumentException; use SqlFormatter; @@ -404,7 +405,7 @@ private static function interpolateAssertArgs($message, $context) ) { throw new \InvalidArgumentException(\sprintf( __NAMESPACE__ . '::interpolate()\'s $message expects string or Stringable object. %s provided.', - \is_object($message) ? \get_class($message) : \gettype($message) + Php::getDebugType($message) )); } if ( @@ -414,8 +415,8 @@ private static function interpolateAssertArgs($message, $context) ))) === 0 ) { throw new \InvalidArgumentException(\sprintf( - __NAMESPACE__ . '::interpolate()\'s $context expects array or object for $context. %s provided.', - \gettype($context) + __NAMESPACE__ . '::interpolate()\'s $context expects array or object. %s provided.', + Php::getDebugType($context) )); } } diff --git a/src/Debug/css/Debug.css b/src/Debug/css/Debug.css index 842e502c..2f1741f4 100644 --- a/src/Debug/css/Debug.css +++ b/src/Debug/css/Debug.css @@ -1 +1 @@ -.debug{font-size:13px}.debug{position:relative;clear:both;font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;line-height:normal;text-align:left;text-shadow:none;color:#111}.debug *{font-size:inherit;line-height:normal;text-indent:0;color:inherit;margin:0}.debug *:not(i[class^=fa]){font-family:inherit}.debug a{text-decoration:none;color:#00e}.debug a:visited{color:#551a8b}.debug a:focus,.debug a:hover{text-decoration:underline}.debug a:active,.debug a:hover{outline:0;color:#e00}.debug a.file-link{color:inherit}.debug button{color:inherit;cursor:pointer;background-color:#f0f0f0;border-color:#afafaf;font-weight:inherit;height:auto;letter-spacing:initial;line-height:initial;outline:none;padding:1px 7px 2px;text-align:center;text-transform:none;vertical-align:baseline;white-space:initial}.debug button:hover{background-color:#dde6f0}.debug code{padding:2px 4px;background-color:transparent;font-family:Menlo, Monaco, Consolas, "Courier New", monospace !important}.debug pre code{display:block}.debug,.debug div{margin:0;width:auto;padding:0;background-color:transparent;border-radius:0}.debug dl{margin-top:0;margin-bottom:0}.debug dl dt{font-weight:bold}.debug dl:not(.dl-horizontal) dd{margin-left:20px;padding-left:10px;text-indent:-10px}.debug li.no-indent{padding-left:0;text-indent:0}.debug h3{margin-top:0.66em;margin-bottom:0.5em;font-size:1.15em;font-weight:bold}.debug h3:first-child{margin-top:0}.debug hr{color:#111;background-color:#111;border:0;height:1px}.debug img{border:0}.debug input{border-width:1px}.debug input[type=checkbox]{margin:0 0.33em 0 0;cursor:pointer}.debug label{display:block;margin:0;cursor:pointer;font-weight:bold;max-width:none}.debug label.disabled{cursor:default;pointer-events:none}.debug legend{padding:0;float:left;margin-bottom:0.5em;border:0;width:100%;font-size:144%;font-weight:bold}.debug p{margin-top:0.25em;margin-bottom:0}.debug p:first-child{margin-top:0}.debug pre{padding:0;border:0;margin:0;white-space:pre;-moz-tab-size:3;-o-tag-size:3;-tab-size:3}.debug ul{margin-top:0;margin-bottom:0}.debug ul.list-unstyled{list-style:none outside none;padding-left:0}.debug ul.list-unstyled>li{text-indent:-1em;padding-left:1em}.debug ul.list-unstyled>ul{margin-left:10px}.debug ul.no-indent>li{padding-left:0;text-indent:0}.debug fieldset{padding:0.66em;margin:0 0 10px 0;min-width:0;border:1px solid black;border-radius:4px}.debug fieldset>ul{font-size:125%}.debug .close{opacity:1;float:none}.debug ul.debug-log-summary{margin-bottom:0.75em}.debug ul.debug-log-summary:empty{margin-bottom:0}.debug .tab-pane>*>.group-body,.debug .m_groupSummary>ul{list-style:none;margin-left:0;border-left:0;padding-left:0}.debug li.php-shutdown{display:block;border-bottom:#31708f solid 1px}.debug .fa{line-height:1}.debug .alert,.debug .m_alert{padding:0.66em;margin-bottom:10px;border-radius:4px;border:1px solid transparent}.debug .m_alert{font-size:125%}.debug .m_alert h3{margin-bottom:4px}.debug .m_alert h3:last-child{margin-bottom:0}.debug .m_alert.error-summary .filter-hidden+h3{margin-top:0}.debug .m_alert.alert-dismissible{padding-right:35px}.debug .m_alert.alert-dismissible .close{float:right;position:relative;top:-7px;right:-21px;border:0;padding:0;font-size:21px;font-weight:700;line-height:1;background:none;color:#000;text-shadow:0 1px 0 #fff;opacity:0.2;cursor:pointer}.debug .m_alert.alert-dismissible .close:hover{text-decoration:none;opacity:0.5}.debug .m_alert .alert-link{font-weight:bold}.debug .alert-error{background-color:#ffbaba;border-color:#d8000c;color:#d8000c}.debug .alert-error .alert-link{color:#843534}.debug .alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.debug .alert-info .alert-link{color:#245269}.debug .alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.debug .alert-success .alert-link{color:#2b542c}.debug .alert-warn{background-color:#fefbe5;border-color:#faebcc;color:#8a6d3b}.debug .alert-warn .alert-link{color:#66512c}.debug nav[role=tablist]{display:inline-block;font-size:0;line-height:20px;vertical-align:top}.debug nav[role=tablist] .fa{margin-right:0.5em}.debug nav[role=tablist] a{display:inline-block;padding:0 13px;font-size:13px;line-height:20px;vertical-align:top;color:#111;cursor:pointer}.debug nav[role=tablist] a:hover{text-decoration:none}.debug nav[role=tablist] a:hover:not(.active){background:#e6e6e6}.debug nav[role=tablist] a.active{line-height:19px;border-bottom:#4071e1 solid 2px}.debug .tab-panes{overflow:auto}.debug .tab-panes .tab-pane{display:none}.debug .tab-panes .tab-pane.active{display:block}.debug .tab-panes .tab-pane .tab-body{padding:10px 12px 5px;background-color:#fff;overflow:auto}.debug .namespace{opacity:0.5}.debug .array-inner,.debug .object-inner{display:block;margin-left:1em}.debug .classname{font-weight:bold;color:#8d0c4c}.debug .attribute .t_punct{color:inherit;font-weight:bold}.debug .t_parameter-name{color:#263}.debug .t_array>.t_array-collapse,.debug .t_array>.array-inner,.debug .t_array>.t_punct{display:none}.debug .t_array.expanded>.t_array-expand{display:none}.debug .t_array.expanded>.t_array-collapse,.debug .t_array.expanded>.t_punct{display:inline}.debug .t_array.expanded>.array-inner{display:block}.debug .t_array.array-file-tree .array-inner{margin-left:0.25em}.debug .t_array.array-file-tree .exclude-count{background:#d9edf7;color:#31708f}.debug .t_array.array-file-tree .t_key{color:#000040;font-weight:bold}.debug .t_array.array-file-tree .t_string::before,.debug .t_array.array-file-tree .t_string::after,.debug .t_array.array-file-tree .t_key::before,.debug .t_array.array-file-tree .t_key::after{display:none}.debug .array-inner>li>.t_operator{margin:0 0.25em}.debug li[class*=m_]>.t_array.array-file-tree>.array-inner{margin-left:-10px}.debug .t_object{display:inline}.debug .t_object .t_modifier_public{color:inherit}.debug .t_object .t_modifier_protected{color:rgba(0,0,0,0.776471)}.debug .t_object .t_modifier_private{color:rgba(0,0,0,0.509804)}.debug .t_object .t_modifier_final{color:rgba(255,0,0,0.933333);font-weight:bold}.debug .t_object .t_modifier_static{font-style:italic;color:rgba(218,13,135,0.933333)}.debug .t_object .t_modifier_debug{color:rgba(0,11,155,0.933333)}.debug .t_object>.object-inner>.t_modifier_final{color:#d8000c;background-color:#ffeded;border-bottom:solid 1px #d8000c;padding:0.25em 0.25em 1px}.debug .t_object .vis-toggles *[data-toggle]{padding:0.15em 0.5em;display:inline-block}.debug .t_object .method .parameter .t_parameter-name[title],.debug .t_object .t_identifier[title],.debug .t_object .t_type[title]{text-decoration:underline}.debug .t_object .method.deprecated{opacity:0.66}.debug .t_object .method.deprecated i{opacity:0.66;color:#d8000c;border-bottom:0}.debug .t_object .method>.t_punct:not(.t_colon){opacity:1;font-weight:bold;color:inherit}.debug .t_object .method i.fa-clone{color:#999}.debug .t_object .private-ancestor:not(:hover)>*{opacity:0.5}.debug .t_object .private-ancestor:not(:hover)>.fa-lock,.debug .t_object .private-ancestor:not(:hover)>.t_modifier_private{opacity:1}.debug .t_object i.fa-warning{color:red}.debug .t_object i.fa-eye{color:rgba(0,11,155,0.933333);font-size:1.1em;border-bottom:0}.debug .t_object i.fa-magic,.debug .t_object .t_modifier_magic,.debug .t_object .t_modifier_magic-read,.debug .t_object .t_modifier_magic-write{color:rgba(255,136,0,0.933333)}.debug .t_object .debugInfo-excluded>i.fa-eye-slash{color:#999}.debug .t_object .info{display:inline-block;background-color:#d9edf7;color:#31708f}.debug td.t_object{display:table-cell}.debug .m_assert,.debug .m_clear,.debug .m_count,.debug .m_error,.debug .m_groupEndValue,.debug .m_info,.debug .m_log,.debug .m_warn{position:relative;display:table;padding-left:10px;text-indent:-10px;padding-right:0.33em;word-break:break-word}.debug .m_table td,.debug .m_trace td{word-break:break-word}.debug .m_table td.t_string,.debug .m_trace td.t_string{padding-left:1em;text-indent:-0.75em}.debug .m_assert{background-color:rgba(255,204,204,0.75)}.debug .m_assert>i{margin-right:0.33em;margin-bottom:-0.2em;display:inline-block;line-height:0.6em;vertical-align:text-bottom}.debug .m_group .group-header{display:table;white-space:nowrap}.debug .m_group .group-header i.fa-warning{color:#cdcb06;margin-left:0.33em}.debug .m_group .group-header i.fa-times-circle{color:#d8000c;margin-left:0.33em}.debug .m_group .group-body{display:none}.debug .m_group>ul{list-style:none;margin-left:1em;border-left:1px solid rgba(0,0,0,0.25);padding-left:0.25rem}.debug .m_group.expanded>.group-body{display:block}.debug .m_error,.debug .m_group.level-error>.group-body,.debug .m_group.level-error:not(.expanded)>.group-header>.level-error{background-color:#ffbaba;color:#d8000c}.debug .m_info,.debug .m_group.level-info>.group-body,.debug .m_group.level-info:not(.expanded)>.group-header>.level-info{background-color:#d9edf7;color:#31708f}.debug .m_trace .classname{color:#146314}.debug .m_warn,.debug .m_group.level-warn>.group-body,.debug .m_group.level-warn:not(.expanded)>.group-header>.level-warn{background-color:#fefbe5;color:#8a6d3b}.debug li[data-channel="general.phpError"]>i+.t_string:nth-child(2){font-weight:bold}.debug li[data-channel="general.phpError"]>.t_string:nth-child(4){opacity:0.7}.debug li[data-channel="general.phpError"]>.t_string:nth-child(4)::before{content:"\A"}.debug li[data-channel="general.phpError"]>.t_string:nth-child(4)::after{content:none}.debug li[data-channel="general.phpError"].error-fatal{padding:10px 10px 10px 20px;border-left:solid 2px #d8000c}.debug li[data-channel="general.phpError"].error-fatal>.t_string:nth-child(2){display:inline-block;margin-bottom:5px;vertical-align:top;font-size:1.2em}.debug li[data-channel="general.phpError"].error-fatal>.t_string:nth-child(3)::before{content:"\A"}.debug table{width:auto;border-collapse:collapse}.debug table caption{caption-side:top;font-weight:bold;font-style:italic;padding-bottom:0;padding-top:2px;text-align:left}.debug table th,.debug table td{padding:0 0.25em;vertical-align:top}.debug table th.t_key{white-space:nowrap}.debug table th.t_key::before,.debug table th.t_key::after{content:none}.debug table td.classname{font-weight:bold}.debug table td.t_undefined{background-color:rgba(0,0,0,0.1)}.debug table td.t_undefined::after{content:none}.debug table th,.debug table tfoot td{font-weight:bold;background-color:rgba(0,0,0,0.1)}.debug table thead th{text-align:center}.debug table thead th .classname{opacity:0.5;font-style:italic}.debug table thead th .classname::before{content:"("}.debug table thead th .classname::after{content:")"}.debug table tr[data-toggle]{cursor:default}.debug table tr[data-toggle]:hover{color:#212529;background-color:rgba(0,0,0,0.075)}.debug table tbody th.t_int,.debug table td[data-type-more=numeric],.debug table td.timestamp,.debug table td.t_int{text-align:right;white-space:nowrap}.debug table.table-bordered th,.debug table.table-bordered td{border:1px solid #7f7f7f;padding:1px 0.25em}.debug table.table-hover tbody tr{cursor:default}.debug table.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,0.075)}.debug table.table-sort>thead th{cursor:default}.debug table.table-sort>thead th:hover{background-color:rgba(0,0,0,0.25)}.debug table.table-sort .sort-arrows{text-align:center;height:1.5em;width:1.2em;margin-left:0;margin-right:1px}.debug table.table-sort .sort-arrows .fa{position:absolute;opacity:0.33}.debug table.table-sort .sort-arrows .fa-caret-down{bottom:0}.debug table.table-sort .sort-arrows .fa-caret-up{top:-1px}.debug table.table-sort .sort-asc .fa-caret-down{opacity:1}.debug table.table-sort .sort-desc .fa-caret-up{opacity:1}.debug table.trace-context{width:100%}.debug table.trace-context tr.context{display:none}.debug table.trace-context tr.context td{color:#111;max-width:1px;background-color:#f5f2f0;padding:0.75em}.debug table.trace-context tr.context td hr{margin:1em 0}.debug .t_identifier{font-weight:bold;white-space:nowrap}.debug .t_type{color:#693}.debug .t_bool[data-type-more=true]{color:#993;text-shadow:1px 1px 2px rgba(153,153,51,0.5)}.debug .t_bool[data-type-more=false]{color:#c33;text-shadow:1px 1px 2px rgba(204,51,51,0.5)}.debug .t_callable{font-weight:bold}.debug .t_callable .t_type,.debug .t_callable .namespace{font-weight:normal}.debug .t_const{color:#039;font-family:monospace}.debug .t_const .t_identifier{color:inherit}.debug .t_int,.debug .t_float,.debug .t_string[data-type-more=numeric],.debug .t_string[data-type-more=timestamp]{font-family:Courier New,monospace,Ariel !important;color:#009;font-size:1.15em;line-height:1.15em}.debug .t_int::before,.debug .t_int::after,.debug .t_float::before,.debug .t_float::after,.debug .t_string[data-type-more=numeric]::before,.debug .t_string[data-type-more=numeric]::after,.debug .t_string[data-type-more=timestamp]::before,.debug .t_string[data-type-more=timestamp]::after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:0.8695em}.debug .t_key{opacity:0.75}.debug .t_key[data-file]{opacity:1}.debug .t_key::before,.debug .t_key::after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;color:#999}.debug .t_key::before{content:'[';margin-right:1px}.debug .t_key::after{content:']';margin-left:1px}.debug .t_key.t_int{white-space:nowrap}.debug .t_keyword{color:#07a}.debug .t_null{opacity:0.3}.debug .t_operator{color:#a67f59;white-space:nowrap !important}.debug .t_punct{color:#999}.debug .excluded,.debug .t_maxDepth,.debug .t_notInspected,.debug .t_recursion,.debug .t_unknown{font-weight:bold;color:red}.debug .t_resource{font-style:italic}.debug .t_string{white-space:pre-wrap;word-break:break-all}.debug .t_string::before,.debug .t_string::after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;opacity:0.33;color:#333}.debug .t_string::before{content:open-quote}.debug .t_string::after{content:close-quote}.debug .t_string.classname::before{content:none}.debug .t_string.classname::after{content:none}.debug .t_string>*{white-space:normal;word-break:break-word}.debug .t_string pre{white-space:pre}.debug .t_stringified{text-shadow:0 0 2px rgba(0,200,200,0.6)}.debug .t_undefined::after{content:"undefined";opacity:0.3}.debug.debug-drawer{position:fixed;bottom:0;left:0;width:100%;background-color:#fff;z-index:1049;height:100px;transform:translateY(100px);transition:all 0.5s}.debug.debug-drawer>*{box-sizing:border-box}.debug.debug-drawer .debug-pull-tab{display:inline-block;opacity:1;position:absolute;height:25px;top:-25px;margin-left:20px;border:1px solid #000;border-bottom-width:0;border-radius:5px 5px 0 0;padding:4px 14px;font-size:16px;background:#f3f3f3;cursor:pointer;transition:all 0.5s}.debug.debug-drawer .debug-pull-tab:hover{background:#dadada}.debug.debug-drawer .debug-pull-tab .debug-error-counts{margin-left:0.5em}.debug.debug-drawer .debug-pull-tab .badge{display:none}.debug.debug-drawer .debug-pull-tab .fa-spinner{display:none}.debug.debug-drawer .debug-resize-handle{display:block;position:absolute;top:-3px;height:4px;width:100%;opacity:0;border-top:1px solid #d0d0d0;border-bottom:1px solid #d0d0d0;background:#dadada;transition:all 0.25s ease-out}.debug.debug-drawer.debug-drawer-open{transform:translateY(0);height:auto}.debug.debug-drawer.debug-drawer-open .debug-pull-tab{opacity:0;transform:translateY(25px)}.debug.debug-drawer.debug-drawer-open .debug-pull-tab .fa-spinner{display:inline-block}.debug.debug-drawer.debug-drawer-open .debug-resize-handle{cursor:ns-resize}.debug.debug-drawer.debug-drawer-open .debug-resize-handle:hover{opacity:1}html.debug-resizing{cursor:ns-resize !important}html.debug-resizing .debug-drawer{transition:none}html.debug-resizing .debug-drawer .debug-resize-handle{opacity:1}.debug .debug-bar{position:relative;padding:5px;font-size:115%;background-color:rgba(0,0,0,0.2);margin-bottom:10px}.debug .debug-bar a{color:#6e6e6e}.debug .debug-bar button{border-radius:0;border-top-width:0;border-bottom-width:0}.debug .debug-bar button.close{font-size:21px;font-weight:300}.debug .debug-bar .float-right{position:absolute;top:0;right:6px;line-height:23px}.debug .debug-bar .float-right button{height:18px;border:0;padding:0;line-height:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.debug .debug-error-counts{position:relative;top:-2px;font-size:0.78em}.debug .debug-error-counts .badge{padding:0 0.4em 0 0.25em;color:inherit;background:inherit;font-size:inherit;vertical-align:unset}.debug .debug-bar{border-top:1px solid #d0d0d0;border-bottom:1px solid #d0d0d0;color:#6e6e6e;padding:0 34px 0 6px;background:#f3f3f3;font-size:16px;margin-bottom:0}.debug .debug-bar a:focus,.debug .debug-bar a:hover,.debug .debug-bar button:focus,.debug .debug-bar button:hover,.debug .debug-bar label:focus,.debug .debug-bar label:hover{color:#333;text-decoration:none}.debug .debug-bar nav{margin-left:20px;line-height:23px}.debug .debug-bar nav a{line-height:23px}.debug .debug-bar nav a.has-assert{line-height:22px;border-bottom:#da8585 solid 2px}.debug .debug-bar nav a.has-warn{line-height:22px;border-bottom:#e4cb0f solid 2px}.debug .debug-bar nav a.has-error{line-height:22px;border-bottom:red solid 2px}.debug .debug-bar nav a.active{line-height:22px;border-bottom:#4071e1 solid 2px}.debug .debug-bar>span{display:inline-block;line-height:23px}.debug .debug-options-toggle{position:relative;top:-1px}.debug .debug-options{position:absolute;top:100%;right:0;max-height:0;box-sizing:border-box;z-index:1000;float:right;min-width:12em;background:#f3f3f3;box-shadow:0 4px 8px rgba(0,0,0,0.25);transition:all 0.25s ease-out;overflow:hidden;font-size:13px}.debug .debug-options .debug-options-body{background-clip:padding-box;border:1px solid #d0d0d0;padding:0.5em 5px 0.5em 15px}.debug .debug-options.show{max-height:150px}.debug .debug-options label{font-weight:normal;padding:0.25em 0}.debug .debug-options label.disabled{color:#999}.debug .debug-options hr.dropdown-divider{margin:0.5em -5px 0.5em -15px;background:#d0d0d0;height:1px;border:none}.debug .debug-options .form-group{margin:0}.debug{transform:scale(1)}.debug .badge{display:inline-block;padding:0.25em 0.4em 0.16em;font-size:82%;font-weight:500;line-height:1;text-align:center;white-space:nowrap;vertical-align:bottom;border-radius:0.25rem;color:#fff;background-color:#666}.debug .debug-sidebar{position:absolute;box-sizing:border-box;width:126px;background:#f3f3f3;height:100%;transform:translateX(-119px);transition:transform .33s}.debug .debug-sidebar button{width:100%}.debug .debug-sidebar input[type=checkbox]{display:none}.debug .debug-sidebar label{font-weight:normal}.debug .debug-sidebar+.tab-body{margin-left:6px;padding-left:10px;transition:margin-left .33s}.debug .debug-sidebar.no-transition+.tab-body{transition:none}.debug .debug-sidebar .collapse{display:none}.debug .debug-sidebar .sidebar-content{padding:0 11px 0 4px;opacity:0;overflow:hidden}.debug .debug-sidebar.show{transform:translateX(0)}.debug .debug-sidebar.show .expand{display:none}.debug .debug-sidebar.show .collapse{display:block}.debug .debug-sidebar.show+.tab-body{margin-left:126px}.debug .debug-sidebar.show .sidebar-content{opacity:1;transition:opacity 0.33s}.debug .debug-sidebar .sidebar-toggle{position:absolute;box-sizing:border-box;right:0;top:0;height:100%;width:7px;background:#f3f3f3;border-left:1px solid #d0d0d0;border-right:1px solid #d0d0d0;cursor:pointer;display:flex;color:#d0d0d0;text-align:center;z-index:1}.debug .debug-sidebar .sidebar-toggle:hover{color:#6e6e6e;background:#dadada}.debug .debug-sidebar .sidebar-toggle>div{margin:auto;padding-left:1px}.debug .debug-sidebar .debug-filters{position:relative;margin:0 -4px 10px -4px}.debug .debug-sidebar .debug-filters ul{margin-left:0}.debug .debug-sidebar .debug-filters li{text-indent:0;padding-left:10px}.debug .debug-sidebar .debug-filters>li{padding-left:4px}.debug .debug-sidebar .debug-filters>li>*:first-child{padding-top:3px;padding-bottom:3px}.debug .debug-sidebar .debug-filters>li>ul{margin-left:-10px}.debug .debug-sidebar .debug-filters>li>ul>li{padding-left:32px}.debug .debug-sidebar .debug-filters label{padding:2px 0 2px 100%;white-space:nowrap}.debug .debug-sidebar .debug-filters label.disabled span{opacity:0.5}.debug .debug-sidebar .debug-filters label,.debug .debug-sidebar .debug-filters ul ul{margin-left:-100%;padding-left:100%}.debug .debug-sidebar .fa-times-circle{color:#d8000c}.debug .debug-sidebar .fa-warning{color:#8a6d3b}.debug .debug-sidebar .fa-info-circle{color:#31708f}.debug .debug-sidebar .toggle{cursor:pointer}.debug .debug-sidebar .toggle.active{background:#dadada}.debug .debug-sidebar .toggle.active:hover{background:#bacce0}.debug .debug-sidebar .toggle:hover,.debug .debug-sidebar .toggle:hover+ul{background:#dde6f0}.debug .debug-sidebar .toggle:hover .toggle.active,.debug .debug-sidebar .toggle:hover+ul .toggle.active{background:#bacce0}.debug i.fa{margin-right:0.33em}.debug i.fa-lg{font-size:1.33em}.debug i.fa-plus-circle{opacity:0.42}.debug i.fa-calendar{font-size:1.1em}.debug .filter-hidden{display:none}.debug .filter-hidden.m_group{display:list-item}.debug .filter-hidden.m_group>.group-header{display:none}.debug .filter-hidden.m_group>.group-body{display:block !important;margin-left:0;border-left:0;padding-left:0}.debug .filter-hidden.m_group.filter-hidden-body>.group-body{display:none !important}.debug .empty.hide-if-empty{display:none}.debug .empty.m_group .group-header{cursor:auto}.debug .vis-toggles span:hover,.debug [data-toggle=interface]:hover{background-color:rgba(0,0,0,0.1)}.debug .vis-toggles .toggle-off,.debug .interface .toggle-off{color:#777}.debug .show-more-container{display:inline}.debug .show-more-wrapper{display:block;position:relative;height:70px;overflow:hidden}.debug .show-more-fade{position:absolute;bottom:-1px;width:100%;height:55px;background-image:linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0.75));pointer-events:none}.debug .level-error .show-more-fade,.debug .m_error .show-more-fade{background-image:linear-gradient(to bottom, rgba(255,186,186,0), #ffbaba)}.debug .level-info .show-more-fade,.debug .m_info .show-more-fade{background-image:linear-gradient(to bottom, rgba(217,237,247,0), #d9edf7)}.debug .level-warn .show-more-fade,.debug .m_warn .show-more-fade{background-image:linear-gradient(to bottom, rgba(254,251,229,0), #fefbe5)}.debug [title]:hover .show-more-fade{background-image:linear-gradient(to bottom, rgba(201,201,201,0), #c9c9c9)}.debug .show-more,.debug .show-less{display:table;box-shadow:1px 1px 0 0 rgba(0,0,0,0.2);border:1px solid rgba(0,0,0,0.2);border-radius:2px;background-color:#eee}.debug-noti-wrap{position:fixed;display:none;top:0;width:100%;height:100%;pointer-events:none;z-index:1050}.debug-noti-wrap .debug-noti{display:table-cell;text-align:center;vertical-align:bottom;font-size:30px;transform-origin:50% 100%}.debug-noti-wrap .debug-noti.animate{animation-duration:1s;animation-name:expandAndFade;animation-timing-function:ease-in}.debug-noti-table{display:table;width:100%;height:100%}@keyframes expandAndFade{from{opacity:0.9;transform:scale(0.9, 0.94)}to{opacity:0;transform:scale(1, 1)}}.debug .expand-all{margin-bottom:0.5em}.debug .file-link.lpad{margin-left:0.33em}.debug .file-link i{margin-right:0;vertical-align:baseline}.debug .hasTooltip,.debug *[title]:not(a){cursor:help}.debug .hasTooltip:hover,.debug *[title]:not(a):hover{background-color:rgba(0,0,0,0.05)}.debug *[data-toggle]{cursor:pointer}.debug .string-encoded.tabs-container>i{line-height:20px;margin-right:0}.debug .string-encoded[data-type=base64]>.string-raw .t_string{font-family:monospace}.debug .prettified{color:rgba(0,11,155,0.933333)}.debug .timestamp{color:#009}.debug .binary,.debug .maxlen,.debug .unicode{margin:0 0.1em;padding:0 0.3em;background-color:silver;color:#003;font-weight:bold}.debug .maxlen{background-color:#fc7}.debug .unicode{background-color:#c0c0ff}.debug .c1-control{display:inline-block;vertical-align:baseline;clip-path:inset(30% 0);transform:scale(2)}.debug .ws_s,.debug .ws_t,.debug .ws_r,.debug .ws_n,.debug .ws_p{opacity:0.33}.debug .ws_t::before{display:inline-block;content:"\203A";width:1em}.debug .ws_r::before{content:"\\r"}.debug .ws_n::before{content:"\\n"}.debug .tippy-box{background-color:#fff;background-clip:padding-box;border:2px solid rgba(0,8,16,0.3);border-radius:4px;outline:0;transition-property:transform, visibility, opacity;color:#333;box-shadow:0 4px 14px -2px rgba(0,8,16,0.08)}.debug .tippy-box .tippy-content{padding:5px 9px;z-index:1}.debug .tippy-box>.tippy-backdrop{background-color:#fff}.debug .tippy-box>.tippy-arrow{width:16px;height:16px;color:#333}.debug .tippy-box>.tippy-arrow::before{content:"";position:absolute;border-color:transparent;border-style:solid}.debug .tippy-box>.tippy-arrow::after,.debug .tippy-box>.tippy-svg-arrow::after{content:'';position:absolute;z-index:-1}.debug .tippy-box>.tippy-arrow::after{border-color:transparent;border-style:solid}.debug .tippy-box[data-placement^='top']>.tippy-arrow{bottom:1px}.debug .tippy-box[data-placement^='top']>.tippy-arrow::before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:#fff;transform-origin:center top}.debug .tippy-box[data-placement^='top']>.tippy-arrow::after{border-top-color:rgba(0,8,16,0.5);border-width:7px 7px 0;top:17px;left:1px}.debug .tippy-box[data-placement^='top']>.tippy-svg-arrow>svg{top:16px}.debug .tippy-box[data-placement^='top']>.tippy-svg-arrow::after{top:17px}.debug .tippy-box[data-placement^='bottom']>.tippy-arrow{top:0}.debug .tippy-box[data-placement^='bottom']>.tippy-arrow::before{top:-6px;left:0;border-width:0 8px 8px;border-bottom-color:#fff;transform-origin:center bottom}.debug .tippy-box[data-placement^='bottom']>.tippy-arrow::after{border-bottom-color:rgba(0,8,16,0.5);border-width:0 7px 7px;bottom:17px;left:1px}.debug .tippy-box[data-placement^='bottom']>.tippy-svg-arrow>svg{bottom:16px}.debug .tippy-box[data-placement^='bottom']>.tippy-svg-arrow::after{bottom:17px}.debug .tippy-box[data-placement^='left']>.tippy-arrow::before{border-left-color:#fff}.debug .tippy-box[data-placement^='left']>.tippy-arrow::after{border-left-color:rgba(0,8,16,0.5);border-width:7px 0 7px 7px;left:17px;top:1px}.debug .tippy-box[data-placement^='left']>.tippy-svg-arrow>svg{left:11px}.debug .tippy-box[data-placement^='left']>.tippy-svg-arrow::after{left:12px}.debug .tippy-box[data-placement^='right']>.tippy-arrow::before{border-right-color:#fff;right:16px}.debug .tippy-box[data-placement^='right']>.tippy-arrow::after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:rgba(0,8,16,0.5)}.debug .tippy-box[data-placement^='right']>.tippy-svg-arrow>svg{right:11px}.debug .tippy-box[data-placement^='right']>.tippy-svg-arrow::after{right:12px}.debug .tippy-box>.tippy-svg-arrow{fill:white}.debug .tippy-box>.tippy-svg-arrow::after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiIC8+PC9zdmc+);background-size:16px 6px;width:16px;height:6px}.debug .indent{padding-left:10px !important}.debug .no-quotes::before{content:none}.debug .no-quotes::after{content:none}.debug .p0{padding:0 !important}.debug .fa-inverse{color:#fff}.debug .fa-stack{line-height:2em}.debug .fa-stack-1x{line-height:inherit}.debug .fa-stack-2x{font-size:2em}.debug .float-left{float:left !important}.debug .float-right{float:right !important}.debug .font-weight-bold{font-weight:bold}.debug .text-center{text-align:center}.debug .text-muted{color:#777}.debug .text-left{text-align:left !important}.debug .text-right{text-align:right !important}.debug .text-error{color:#d8000c}.debug .text-info{color:#31708f}.debug .text-warn{color:#8a6d3b} +.debug{font-size:13px}.debug{position:relative;clear:both;font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;line-height:normal;text-align:left;text-shadow:none;color:#111}.debug *{font-size:inherit;line-height:normal;text-indent:0;color:inherit;margin:0}.debug *:not(i[class^=fa]){font-family:inherit}.debug a{text-decoration:none;color:#00e}.debug a:visited{color:#551a8b}.debug a:focus,.debug a:hover{text-decoration:underline}.debug a:active,.debug a:hover{outline:0;color:#e00}.debug a.file-link{color:inherit}.debug button{color:inherit;cursor:pointer;background-color:#f0f0f0;border-color:#afafaf;font-weight:inherit;height:auto;letter-spacing:initial;line-height:initial;outline:none;padding:1px 7px 2px;text-align:center;text-transform:none;vertical-align:baseline;white-space:initial}.debug button:hover{background-color:#dde6f0}.debug code{padding:2px 4px;background-color:transparent;font-family:Menlo, Monaco, Consolas, "Courier New", monospace !important}.debug pre code{display:block}.debug,.debug div{margin:0;width:auto;height:auto;padding:0;background-color:transparent;border-radius:0}.debug dl{margin-top:0;margin-bottom:0}.debug dl dt{font-weight:bold}.debug dl:not(.dl-horizontal) dd{margin-left:20px;padding-left:10px;text-indent:-10px}.debug li.no-indent{padding-left:0;text-indent:0}.debug h3{margin-top:0.66em;margin-bottom:0.5em;font-size:1.15em;font-weight:bold}.debug h3:first-child{margin-top:0}.debug hr{color:#111;background-color:#111;border:0;height:1px}.debug img{border:0}.debug input{border-width:1px}.debug input[type=checkbox]{margin:0 0.33em 0 0;cursor:pointer}.debug label{display:block;margin:0;cursor:pointer;font-weight:bold;max-width:none}.debug label.disabled{cursor:default;pointer-events:none}.debug legend{padding:0;float:left;margin-bottom:0.5em;border:0;width:100%;font-size:144%;font-weight:bold}.debug p{margin-top:0.25em;margin-bottom:0}.debug p:first-child{margin-top:0}.debug pre{padding:0;border:0;margin:0;white-space:pre;-moz-tab-size:3;-o-tag-size:3;-tab-size:3}.debug ul{margin-top:0;margin-bottom:0}.debug ul.list-unstyled{list-style:none outside none;padding-left:0}.debug ul.list-unstyled>li{text-indent:-1em;padding-left:1em}.debug ul.list-unstyled>ul{margin-left:10px}.debug ul.no-indent>li{padding-left:0;text-indent:0}.debug fieldset{padding:0.66em;margin:0 0 10px 0;min-width:0;border:1px solid black;border-radius:4px}.debug fieldset>ul{font-size:125%}.debug .close{opacity:1;float:none}.debug ul.debug-log-summary{margin-bottom:0.75em}.debug ul.debug-log-summary:empty{margin-bottom:0}.debug .tab-pane>*>.group-body,.debug .m_groupSummary>ul{list-style:none;margin-left:0;border-left:0;padding-left:0}.debug li.php-shutdown{display:block;border-bottom:#31708f solid 1px}.debug .fa{line-height:1}.debug .alert,.debug .m_alert{padding:0.66em;margin-bottom:10px;border-radius:4px;border:1px solid transparent}.debug .m_alert{font-size:125%}.debug .m_alert h3{margin-bottom:4px}.debug .m_alert h3:last-child{margin-bottom:0}.debug .m_alert.error-summary .filter-hidden+h3{margin-top:0}.debug .m_alert.alert-dismissible{padding-right:35px}.debug .m_alert.alert-dismissible .close{float:right;position:relative;top:-7px;right:-21px;border:0;padding:0;font-size:21px;font-weight:700;line-height:1;background:none;color:#000;text-shadow:0 1px 0 #fff;opacity:0.2;cursor:pointer}.debug .m_alert.alert-dismissible .close:hover{text-decoration:none;opacity:0.5}.debug .m_alert .alert-link{font-weight:bold}.debug .alert-error{background-color:#ffbaba;border-color:#d8000c;color:#d8000c}.debug .alert-error .alert-link{color:#843534}.debug .alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.debug .alert-info .alert-link{color:#245269}.debug .alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.debug .alert-success .alert-link{color:#2b542c}.debug .alert-warn{background-color:#fefbe5;border-color:#faebcc;color:#8a6d3b}.debug .alert-warn .alert-link{color:#66512c}.debug nav[role=tablist]{display:inline-block;font-size:0;line-height:20px;vertical-align:top}.debug nav[role=tablist] .fa{margin-right:0.5em}.debug nav[role=tablist] a{display:inline-block;padding:0 13px;font-size:13px;line-height:20px;vertical-align:top;color:#111;cursor:pointer}.debug nav[role=tablist] a:hover{text-decoration:none}.debug nav[role=tablist] a:hover:not(.active){background:#e6e6e6}.debug nav[role=tablist] a.active{line-height:19px;border-bottom:#4071e1 solid 2px}.debug .tab-panes{overflow:auto}.debug .tab-panes .tab-pane{position:static;display:none}.debug .tab-panes .tab-pane.active{display:block}.debug .tab-panes .tab-pane .tab-body{padding:10px 12px 5px;background-color:#fff;overflow:auto}.debug .namespace{opacity:0.5}.debug .array-inner,.debug .object-inner{display:block;margin-left:1em}.debug .classname{font-weight:bold;color:#8d0c4c}.debug .attribute .t_punct{color:inherit;font-weight:bold}.debug .t_parameter-name{color:#263}.debug .t_array>.t_array-collapse,.debug .t_array>.array-inner,.debug .t_array>.t_punct{display:none}.debug .t_array.expanded>.t_array-expand{display:none}.debug .t_array.expanded>.t_array-collapse,.debug .t_array.expanded>.t_punct{display:inline}.debug .t_array.expanded>.array-inner{display:block}.debug .t_array.array-file-tree .array-inner{margin-left:0.25em}.debug .t_array.array-file-tree .exclude-count{background:#d9edf7;color:#31708f}.debug .t_array.array-file-tree .t_key{color:#000040;font-weight:bold}.debug .t_array.array-file-tree .t_string::before,.debug .t_array.array-file-tree .t_string::after,.debug .t_array.array-file-tree .t_key::before,.debug .t_array.array-file-tree .t_key::after{display:none}.debug .array-inner>li>.t_operator{margin:0 0.25em}.debug li[class*=m_]>.t_array.array-file-tree>.array-inner{margin-left:-10px}.debug .t_object{display:inline}.debug .t_object .t_modifier_public{color:inherit}.debug .t_object .t_modifier_protected{color:rgba(0,0,0,0.776471)}.debug .t_object .t_modifier_private{color:rgba(0,0,0,0.509804)}.debug .t_object .t_modifier_final{color:rgba(255,0,0,0.933333);font-weight:bold}.debug .t_object .t_modifier_static{font-style:italic;color:rgba(218,13,135,0.933333)}.debug .t_object .t_modifier_debug{color:rgba(0,11,155,0.933333)}.debug .t_object>.object-inner>.t_modifier_final{color:#d8000c;background-color:#ffeded;border-bottom:solid 1px #d8000c;padding:0.25em 0.25em 1px}.debug .t_object .vis-toggles *[data-toggle]{padding:0.15em 0.5em;display:inline-block}.debug .t_object .method .parameter .t_parameter-name[title],.debug .t_object .t_identifier[title],.debug .t_object .t_type[title]{text-decoration:underline}.debug .t_object .method.deprecated{opacity:0.66}.debug .t_object .method.deprecated i{opacity:0.66;color:#d8000c;border-bottom:0}.debug .t_object .method>.t_punct:not(.t_colon){opacity:1;font-weight:bold;color:inherit}.debug .t_object .method i.fa-clone{color:#999}.debug .t_object .private-ancestor:not(:hover)>*{opacity:0.5}.debug .t_object .private-ancestor:not(:hover)>.fa-lock,.debug .t_object .private-ancestor:not(:hover)>.t_modifier_private{opacity:1}.debug .t_object i.fa-warning{color:red}.debug .t_object i.fa-eye{color:rgba(0,11,155,0.933333);font-size:1.1em;border-bottom:0}.debug .t_object i.fa-magic,.debug .t_object .t_modifier_magic,.debug .t_object .t_modifier_magic-read,.debug .t_object .t_modifier_magic-write{color:rgba(255,136,0,0.933333)}.debug .t_object .debugInfo-excluded>i.fa-eye-slash{color:#999}.debug .t_object .info{display:inline-block;background-color:#d9edf7;color:#31708f}.debug td.t_object{display:table-cell}.debug .m_assert,.debug .m_clear,.debug .m_count,.debug .m_error,.debug .m_groupEndValue,.debug .m_info,.debug .m_log,.debug .m_warn{position:relative;display:table;padding-left:10px;text-indent:-10px;padding-right:0.33em;word-break:break-word}.debug .m_table td,.debug .m_trace td{word-break:break-word}.debug .m_table td.t_string,.debug .m_trace td.t_string{padding-left:1em;text-indent:-0.75em}.debug .m_assert{background-color:rgba(255,204,204,0.75)}.debug .m_assert>i{margin-right:0.33em;margin-bottom:-0.2em;display:inline-block;line-height:0.6em;vertical-align:text-bottom}.debug .m_group .group-header{display:table;white-space:nowrap}.debug .m_group .group-header i.fa-warning{color:#cdcb06;margin-left:0.33em}.debug .m_group .group-header i.fa-times-circle{color:#d8000c;margin-left:0.33em}.debug .m_group .group-body{display:none}.debug .m_group>ul{list-style:none;margin-left:1em;border-left:1px solid rgba(0,0,0,0.25);padding-left:0.25rem}.debug .m_group.expanded>.group-body{display:block}.debug .m_error,.debug .m_group.level-error>.group-body,.debug .m_group.level-error:not(.expanded)>.group-header>.level-error{background-color:#ffbaba;color:#d8000c}.debug .m_info,.debug .m_group.level-info>.group-body,.debug .m_group.level-info:not(.expanded)>.group-header>.level-info{background-color:#d9edf7;color:#31708f}.debug .m_trace .classname{color:#146314}.debug .m_warn,.debug .m_group.level-warn>.group-body,.debug .m_group.level-warn:not(.expanded)>.group-header>.level-warn{background-color:#fefbe5;color:#8a6d3b}.debug li[data-channel="general.phpError"]>i+.t_string:nth-child(2){font-weight:bold}.debug li[data-channel="general.phpError"]>.t_string:nth-child(4){opacity:0.7}.debug li[data-channel="general.phpError"]>.t_string:nth-child(4)::before{content:"\A"}.debug li[data-channel="general.phpError"]>.t_string:nth-child(4)::after{content:none}.debug li[data-channel="general.phpError"].error-fatal{padding:10px 10px 10px 20px;border-left:solid 2px #d8000c}.debug li[data-channel="general.phpError"].error-fatal>.t_string:nth-child(2){display:inline-block;margin-bottom:5px;vertical-align:top;font-size:1.2em}.debug li[data-channel="general.phpError"].error-fatal>.t_string:nth-child(3)::before{content:"\A"}.debug table{width:auto;border-collapse:collapse}.debug table caption{caption-side:top;font-weight:bold;font-style:italic;padding-bottom:0;padding-top:2px;text-align:left}.debug table th,.debug table td{padding:0 0.25em;vertical-align:top}.debug table th.t_key{white-space:nowrap}.debug table th.t_key::before,.debug table th.t_key::after{content:none}.debug table td.classname{font-weight:bold}.debug table td.t_undefined{background-color:rgba(0,0,0,0.1)}.debug table td.t_undefined::after{content:none}.debug table th,.debug table tfoot td{font-weight:bold;background-color:rgba(0,0,0,0.1)}.debug table thead th{text-align:center}.debug table thead th .classname{opacity:0.5;font-style:italic}.debug table thead th .classname::before{content:"("}.debug table thead th .classname::after{content:")"}.debug table tr[data-toggle]{cursor:default}.debug table tr[data-toggle]:hover{color:#212529;background-color:rgba(0,0,0,0.075)}.debug table tbody th.t_int,.debug table td[data-type-more=numeric],.debug table td.timestamp,.debug table td.t_int{text-align:right;white-space:nowrap}.debug table.table-bordered th,.debug table.table-bordered td{border:1px solid #7f7f7f;padding:1px 0.25em}.debug table.table-hover tbody tr{cursor:default}.debug table.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,0.075)}.debug table.table-sort>thead th{cursor:default}.debug table.table-sort>thead th:hover{background-color:rgba(0,0,0,0.25)}.debug table.table-sort .sort-arrows{text-align:center;height:1.5em;width:1.2em;margin-left:0;margin-right:1px}.debug table.table-sort .sort-arrows .fa{position:absolute;opacity:0.33}.debug table.table-sort .sort-arrows .fa-caret-down{bottom:0}.debug table.table-sort .sort-arrows .fa-caret-up{top:-1px}.debug table.table-sort .sort-asc .fa-caret-down{opacity:1}.debug table.table-sort .sort-desc .fa-caret-up{opacity:1}.debug table.trace-context{width:100%}.debug table.trace-context tr.context{display:none}.debug table.trace-context tr.context td{color:#111;max-width:1px;background-color:#f5f2f0;padding:0.75em}.debug table.trace-context tr.context td hr{margin:1em 0}.debug .t_identifier{font-weight:bold;white-space:nowrap}.debug .t_type{color:#693}.debug .t_bool[data-type-more=true]{color:#993;text-shadow:1px 1px 2px rgba(153,153,51,0.5)}.debug .t_bool[data-type-more=false]{color:#c33;text-shadow:1px 1px 2px rgba(204,51,51,0.5)}.debug .t_callable{font-weight:bold}.debug .t_callable .t_type,.debug .t_callable .namespace{font-weight:normal}.debug .t_const{color:#039;font-family:monospace}.debug .t_const .t_identifier{color:inherit}.debug .t_int,.debug .t_float,.debug .t_string[data-type-more=numeric],.debug .t_string[data-type-more=timestamp]{font-family:Courier New,monospace,Ariel !important;color:#009;font-size:1.15em;line-height:1.15em}.debug .t_int::before,.debug .t_int::after,.debug .t_float::before,.debug .t_float::after,.debug .t_string[data-type-more=numeric]::before,.debug .t_string[data-type-more=numeric]::after,.debug .t_string[data-type-more=timestamp]::before,.debug .t_string[data-type-more=timestamp]::after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:0.8695em}.debug .t_key{opacity:0.75}.debug .t_key[data-file]{opacity:1}.debug .t_key::before,.debug .t_key::after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;color:#999}.debug .t_key::before{content:'[';margin-right:1px}.debug .t_key::after{content:']';margin-left:1px}.debug .t_key.t_int{white-space:nowrap}.debug .t_keyword{color:#07a}.debug .t_null{opacity:0.3}.debug .t_operator{color:#a67f59;white-space:nowrap !important}.debug .t_punct{color:#999}.debug .excluded,.debug .t_maxDepth,.debug .t_notInspected,.debug .t_recursion,.debug .t_unknown{font-weight:bold;color:red}.debug .t_resource{font-style:italic}.debug .t_string{white-space:pre-wrap;word-break:break-all}.debug .t_string::before,.debug .t_string::after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;opacity:0.33;color:#333}.debug .t_string::before{content:open-quote}.debug .t_string::after{content:close-quote}.debug .t_string.classname::before{content:none}.debug .t_string.classname::after{content:none}.debug .t_string>*{white-space:normal;word-break:break-word}.debug .t_string pre{white-space:pre}.debug .t_stringified{text-shadow:0 0 2px rgba(0,200,200,0.6)}.debug .t_undefined::after{content:"undefined";opacity:0.3}.debug.debug-drawer{position:fixed;bottom:0;left:0;width:100%;background-color:#fff;z-index:1049;height:100px;transform:translateY(100px);transition:all 0.5s}.debug.debug-drawer>*{box-sizing:border-box}.debug.debug-drawer .debug-pull-tab{display:inline-block;opacity:1;position:absolute;height:25px;top:-25px;margin-left:20px;border:1px solid #000;border-bottom-width:0;border-radius:5px 5px 0 0;padding:4px 14px;font-size:16px;background:#f3f3f3;cursor:pointer;transition:all 0.5s}.debug.debug-drawer .debug-pull-tab:hover{background:#dadada}.debug.debug-drawer .debug-pull-tab .debug-error-counts{margin-left:0.5em}.debug.debug-drawer .debug-pull-tab .badge{display:none}.debug.debug-drawer .debug-pull-tab .fa-spinner{display:none}.debug.debug-drawer .debug-resize-handle{display:block;position:absolute;top:-3px;height:4px;width:100%;opacity:0;border-top:1px solid #d0d0d0;border-bottom:1px solid #d0d0d0;background:#dadada;transition:all 0.25s ease-out}.debug.debug-drawer.debug-drawer-open{transform:translateY(0);height:auto}.debug.debug-drawer.debug-drawer-open .debug-pull-tab{opacity:0;transform:translateY(25px)}.debug.debug-drawer.debug-drawer-open .debug-pull-tab .fa-spinner{display:inline-block}.debug.debug-drawer.debug-drawer-open .debug-resize-handle{cursor:ns-resize}.debug.debug-drawer.debug-drawer-open .debug-resize-handle:hover{opacity:1}html.debug-resizing{cursor:ns-resize !important}html.debug-resizing .debug-drawer{transition:none}html.debug-resizing .debug-drawer .debug-resize-handle{opacity:1}.debug .debug-bar{position:relative;padding:5px;font-size:115%;background-color:rgba(0,0,0,0.2);margin-bottom:10px}.debug .debug-bar a{color:#6e6e6e}.debug .debug-bar button{border-radius:0;border-top-width:0;border-bottom-width:0}.debug .debug-bar button.close{font-size:21px;font-weight:300}.debug .debug-bar .float-right{position:absolute;top:0;right:6px;line-height:23px}.debug .debug-bar .float-right button{height:18px;border:0;padding:0;line-height:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.debug .debug-error-counts{position:relative;top:-2px;font-size:0.78em}.debug .debug-error-counts .badge{padding:0 0.4em 0 0.25em;color:inherit;background:inherit;font-size:inherit;vertical-align:unset}.debug .debug-bar{border-top:1px solid #d0d0d0;border-bottom:1px solid #d0d0d0;color:#6e6e6e;padding:0 34px 0 6px;background:#f3f3f3;font-size:16px;margin-bottom:0}.debug .debug-bar a:focus,.debug .debug-bar a:hover,.debug .debug-bar button:focus,.debug .debug-bar button:hover,.debug .debug-bar label:focus,.debug .debug-bar label:hover{color:#333;text-decoration:none}.debug .debug-bar nav{margin-left:20px;line-height:23px}.debug .debug-bar nav a{line-height:23px}.debug .debug-bar nav a.has-assert{line-height:22px;border-bottom:#da8585 solid 2px}.debug .debug-bar nav a.has-warn{line-height:22px;border-bottom:#e4cb0f solid 2px}.debug .debug-bar nav a.has-error{line-height:22px;border-bottom:red solid 2px}.debug .debug-bar nav a.active{line-height:22px;border-bottom:#4071e1 solid 2px}.debug .debug-bar>span{display:inline-block;line-height:23px}.debug .debug-options-toggle{position:relative;top:-1px}.debug .debug-options{position:absolute;top:100%;right:0;max-height:0;box-sizing:border-box;z-index:1000;float:right;min-width:12em;background:#f3f3f3;box-shadow:0 4px 8px rgba(0,0,0,0.25);transition:all 0.25s ease-out;overflow:hidden;font-size:13px}.debug .debug-options .debug-options-body{background-clip:padding-box;border:1px solid #d0d0d0;padding:0.5em 5px 0.5em 15px}.debug .debug-options.show{max-height:150px}.debug .debug-options label{font-weight:normal;padding:0.25em 0}.debug .debug-options label.disabled{color:#999}.debug .debug-options hr.dropdown-divider{margin:0.5em -5px 0.5em -15px;background:#d0d0d0;height:1px;border:none}.debug .debug-options .form-group{margin:0}.debug{transform:scale(1)}.debug .badge{display:inline-block;padding:0.25em 0.4em 0.16em;font-size:82%;font-weight:500;line-height:1;text-align:center;white-space:nowrap;vertical-align:bottom;border-radius:0.25rem;color:#fff;background-color:#666}.debug .debug-sidebar{position:absolute;box-sizing:border-box;width:126px;background:#f3f3f3;height:100%;transform:translateX(-119px);transition:transform .33s}.debug .debug-sidebar button{width:100%}.debug .debug-sidebar input[type=checkbox]{display:none}.debug .debug-sidebar label{font-weight:normal}.debug .debug-sidebar+.tab-body{margin-left:6px;padding-left:10px;transition:margin-left .33s}.debug .debug-sidebar.no-transition+.tab-body{transition:none}.debug .debug-sidebar .collapse{display:none}.debug .debug-sidebar .sidebar-content{padding:0 11px 0 4px;opacity:0;overflow:hidden}.debug .debug-sidebar.show{transform:translateX(0)}.debug .debug-sidebar.show .expand{display:none}.debug .debug-sidebar.show .collapse{display:block}.debug .debug-sidebar.show+.tab-body{margin-left:126px}.debug .debug-sidebar.show .sidebar-content{opacity:1;transition:opacity 0.33s}.debug .debug-sidebar .sidebar-toggle{position:absolute;box-sizing:border-box;right:0;top:0;height:100%;width:7px;background:#f3f3f3;border-left:1px solid #d0d0d0;border-right:1px solid #d0d0d0;cursor:pointer;display:flex;color:#d0d0d0;text-align:center;z-index:1}.debug .debug-sidebar .sidebar-toggle:hover{color:#6e6e6e;background:#dadada}.debug .debug-sidebar .sidebar-toggle>div{margin:auto;padding-left:1px}.debug .debug-sidebar .debug-filters{position:relative;margin:0 -4px 10px -4px}.debug .debug-sidebar .debug-filters ul{margin-left:0}.debug .debug-sidebar .debug-filters li{text-indent:0;padding-left:10px}.debug .debug-sidebar .debug-filters>li{padding-left:4px}.debug .debug-sidebar .debug-filters>li>*:first-child{padding-top:3px;padding-bottom:3px}.debug .debug-sidebar .debug-filters>li>ul{margin-left:-10px}.debug .debug-sidebar .debug-filters>li>ul>li{padding-left:32px}.debug .debug-sidebar .debug-filters label{padding:2px 0 2px 100%;white-space:nowrap}.debug .debug-sidebar .debug-filters label.disabled span{opacity:0.5}.debug .debug-sidebar .debug-filters label,.debug .debug-sidebar .debug-filters ul ul{margin-left:-100%;padding-left:100%}.debug .debug-sidebar .fa-times-circle{color:#d8000c}.debug .debug-sidebar .fa-warning{color:#8a6d3b}.debug .debug-sidebar .fa-info-circle{color:#31708f}.debug .debug-sidebar .toggle{cursor:pointer}.debug .debug-sidebar .toggle.active{background:#dadada}.debug .debug-sidebar .toggle.active:hover{background:#bacce0}.debug .debug-sidebar .toggle:hover,.debug .debug-sidebar .toggle:hover+ul{background:#dde6f0}.debug .debug-sidebar .toggle:hover .toggle.active,.debug .debug-sidebar .toggle:hover+ul .toggle.active{background:#bacce0}.debug i.fa{margin-right:0.33em}.debug i.fa-lg{font-size:1.33em}.debug i.fa-plus-circle{opacity:0.42}.debug i.fa-calendar{font-size:1.1em}.debug .filter-hidden{display:none}.debug .filter-hidden.m_group{display:list-item}.debug .filter-hidden.m_group>.group-header{display:none}.debug .filter-hidden.m_group>.group-body{display:block !important;margin-left:0;border-left:0;padding-left:0}.debug .filter-hidden.m_group.filter-hidden-body>.group-body{display:none !important}.debug .empty.hide-if-empty{display:none}.debug .empty.m_group .group-header{cursor:auto}.debug .vis-toggles span:hover,.debug [data-toggle=interface]:hover{background-color:rgba(0,0,0,0.1)}.debug .vis-toggles .toggle-off,.debug .interface .toggle-off{color:#777}.debug .show-more-container{display:inline}.debug .show-more-wrapper{display:block;position:relative;height:70px;overflow:hidden}.debug .show-more-fade{position:absolute;bottom:-1px;width:100%;height:55px;background-image:linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0.75));pointer-events:none}.debug .level-error .show-more-fade,.debug .m_error .show-more-fade{background-image:linear-gradient(to bottom, rgba(255,186,186,0), #ffbaba)}.debug .level-info .show-more-fade,.debug .m_info .show-more-fade{background-image:linear-gradient(to bottom, rgba(217,237,247,0), #d9edf7)}.debug .level-warn .show-more-fade,.debug .m_warn .show-more-fade{background-image:linear-gradient(to bottom, rgba(254,251,229,0), #fefbe5)}.debug [title]:hover .show-more-fade{background-image:linear-gradient(to bottom, rgba(201,201,201,0), #c9c9c9)}.debug .show-more,.debug .show-less{display:table;box-shadow:1px 1px 0 0 rgba(0,0,0,0.2);border:1px solid rgba(0,0,0,0.2);border-radius:2px;background-color:#eee}.debug-noti-wrap{position:fixed;display:none;top:0;width:100%;height:100%;pointer-events:none;z-index:1050}.debug-noti-wrap .debug-noti{display:table-cell;text-align:center;vertical-align:bottom;font-size:30px;transform-origin:50% 100%}.debug-noti-wrap .debug-noti.animate{animation-duration:1s;animation-name:expandAndFade;animation-timing-function:ease-in}.debug-noti-table{display:table;width:100%;height:100%}@keyframes expandAndFade{from{opacity:0.9;transform:scale(0.9, 0.94)}to{opacity:0;transform:scale(1, 1)}}.debug .expand-all{margin-bottom:0.5em}.debug .file-link.lpad{margin-left:0.33em}.debug .file-link i{margin-right:0;vertical-align:baseline}.debug .hasTooltip,.debug *[title]:not(a){cursor:help}.debug .hasTooltip:hover,.debug *[title]:not(a):hover{background-color:rgba(0,0,0,0.05)}.debug *[data-toggle]{cursor:pointer}.debug .string-encoded.tabs-container>i{line-height:20px;margin-right:0}.debug .string-encoded[data-type=base64]>.string-raw .t_string{font-family:monospace}.debug .prettified{color:rgba(0,11,155,0.933333)}.debug .timestamp{color:#009}.debug .binary,.debug .maxlen,.debug .unicode{margin:0 0.1em;padding:0 0.3em;background-color:silver;color:#003;font-weight:bold}.debug .maxlen{background-color:#fc7}.debug .unicode{background-color:#c0c0ff}.debug .c1-control{display:inline-block;vertical-align:baseline;clip-path:inset(30% 0);transform:scale(2)}.debug .ws_s,.debug .ws_t,.debug .ws_r,.debug .ws_n,.debug .ws_p{opacity:0.33}.debug .ws_t::before{display:inline-block;content:"\203A";width:1em}.debug .ws_r::before{content:"\\r"}.debug .ws_n::before{content:"\\n"}.debug .tippy-box{background-color:#fff;background-clip:padding-box;border:2px solid rgba(0,8,16,0.3);border-radius:4px;outline:0;transition-property:transform, visibility, opacity;color:#333;box-shadow:0 4px 14px -2px rgba(0,8,16,0.08)}.debug .tippy-box .tippy-content{padding:5px 9px;z-index:1}.debug .tippy-box>.tippy-backdrop{background-color:#fff}.debug .tippy-box>.tippy-arrow{width:16px;height:16px;color:#333}.debug .tippy-box>.tippy-arrow::before{content:"";position:absolute;border-color:transparent;border-style:solid}.debug .tippy-box>.tippy-arrow::after,.debug .tippy-box>.tippy-svg-arrow::after{content:'';position:absolute;z-index:-1}.debug .tippy-box>.tippy-arrow::after{border-color:transparent;border-style:solid}.debug .tippy-box[data-placement^='top']>.tippy-arrow{bottom:1px}.debug .tippy-box[data-placement^='top']>.tippy-arrow::before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:#fff;transform-origin:center top}.debug .tippy-box[data-placement^='top']>.tippy-arrow::after{border-top-color:rgba(0,8,16,0.5);border-width:7px 7px 0;top:17px;left:1px}.debug .tippy-box[data-placement^='top']>.tippy-svg-arrow>svg{top:16px}.debug .tippy-box[data-placement^='top']>.tippy-svg-arrow::after{top:17px}.debug .tippy-box[data-placement^='bottom']>.tippy-arrow{top:0}.debug .tippy-box[data-placement^='bottom']>.tippy-arrow::before{top:-6px;left:0;border-width:0 8px 8px;border-bottom-color:#fff;transform-origin:center bottom}.debug .tippy-box[data-placement^='bottom']>.tippy-arrow::after{border-bottom-color:rgba(0,8,16,0.5);border-width:0 7px 7px;bottom:17px;left:1px}.debug .tippy-box[data-placement^='bottom']>.tippy-svg-arrow>svg{bottom:16px}.debug .tippy-box[data-placement^='bottom']>.tippy-svg-arrow::after{bottom:17px}.debug .tippy-box[data-placement^='left']>.tippy-arrow::before{border-left-color:#fff}.debug .tippy-box[data-placement^='left']>.tippy-arrow::after{border-left-color:rgba(0,8,16,0.5);border-width:7px 0 7px 7px;left:17px;top:1px}.debug .tippy-box[data-placement^='left']>.tippy-svg-arrow>svg{left:11px}.debug .tippy-box[data-placement^='left']>.tippy-svg-arrow::after{left:12px}.debug .tippy-box[data-placement^='right']>.tippy-arrow::before{border-right-color:#fff;right:16px}.debug .tippy-box[data-placement^='right']>.tippy-arrow::after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:rgba(0,8,16,0.5)}.debug .tippy-box[data-placement^='right']>.tippy-svg-arrow>svg{right:11px}.debug .tippy-box[data-placement^='right']>.tippy-svg-arrow::after{right:12px}.debug .tippy-box>.tippy-svg-arrow{fill:white}.debug .tippy-box>.tippy-svg-arrow::after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiIC8+PC9zdmc+);background-size:16px 6px;width:16px;height:6px}.debug .indent{padding-left:10px !important}.debug .no-quotes::before{content:none}.debug .no-quotes::after{content:none}.debug .p0{padding:0 !important}.debug .fa-inverse{color:#fff}.debug .fa-stack{line-height:2em}.debug .fa-stack-1x{line-height:inherit}.debug .fa-stack-2x{font-size:2em}.debug .float-left{float:left !important}.debug .float-right{float:right !important}.debug .font-weight-bold{font-weight:bold}.debug .text-center{text-align:center}.debug .text-muted{color:#777}.debug .text-left{text-align:left !important}.debug .text-right{text-align:right !important}.debug .text-error{color:#d8000c}.debug .text-info{color:#31708f}.debug .text-warn{color:#8a6d3b} diff --git a/src/Debug/scss/_base.scss b/src/Debug/scss/_base.scss index fbc078d1..f906e091 100644 --- a/src/Debug/scss/_base.scss +++ b/src/Debug/scss/_base.scss @@ -69,6 +69,7 @@ pre code { div { margin: 0; width: auto; + height: auto; padding: 0; background-color: transparent; border-radius: 0; diff --git a/src/Debug/scss/_tabs.scss b/src/Debug/scss/_tabs.scss index 14ed912e..2481faf2 100644 --- a/src/Debug/scss/_tabs.scss +++ b/src/Debug/scss/_tabs.scss @@ -32,6 +32,7 @@ nav[role=tablist] { overflow: auto; .tab-pane { + position: static; display: none; &.active { display: block; diff --git a/src/ErrorHandler/AbstractErrorHandler.php b/src/ErrorHandler/AbstractErrorHandler.php index fa8000d2..69440302 100644 --- a/src/ErrorHandler/AbstractErrorHandler.php +++ b/src/ErrorHandler/AbstractErrorHandler.php @@ -91,12 +91,14 @@ protected function enableStatsEmailer($haveError = false) // no reason to instantiate or subscribe return; } - $errorSubscribers = $this->eventManager->getSubscribers(ErrorHandler::EVENT_ERROR); - if ($this->cfg['enableEmailer'] && \in_array(array($this->getEmailer(), 'onErrorHighPri'), $errorSubscribers, true) === false) { + $callables = \array_map(static function ($subscriberInfo) { + return $subscriberInfo['callable']; + }, $this->eventManager->getSubscribers(ErrorHandler::EVENT_ERROR)); + if ($this->cfg['enableEmailer'] && \in_array(array($this->getEmailer(), 'onErrorHighPri'), $callables, true) === false) { $this->cfg['enableStats'] = true; $this->eventManager->addSubscriberInterface($this->emailer); } - if ($this->cfg['enableStats'] && \in_array(array($this->getStats(), 'onErrorHighPri'), $errorSubscribers, true) === false) { + if ($this->cfg['enableStats'] && \in_array(array($this->getStats(), 'onErrorHighPri'), $callables, true) === false) { $this->eventManager->addSubscriberInterface($this->stats); } } diff --git a/src/ErrorHandler/ErrorHandler.php b/src/ErrorHandler/ErrorHandler.php index c53956e3..6eb3170f 100644 --- a/src/ErrorHandler/ErrorHandler.php +++ b/src/ErrorHandler/ErrorHandler.php @@ -333,9 +333,7 @@ public function setData($key, $value) public function setErrorCaller($caller = null, $offset = 0) { if ($caller === null) { - $backtrace = \version_compare(PHP_VERSION, '5.4.0', '>=') - ? \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $offset + 3) - : \debug_backtrace(false); // don't provide object + $backtrace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $offset + 3); $index = isset($backtrace[$offset + 1]) ? $offset + 1 : \count($backtrace) - 1; diff --git a/src/HttpMessage/AbstractServerRequest.php b/src/HttpMessage/AbstractServerRequest.php index 717ad119..e427be09 100644 --- a/src/HttpMessage/AbstractServerRequest.php +++ b/src/HttpMessage/AbstractServerRequest.php @@ -78,7 +78,7 @@ public static function parseStrOpts($mixed, $val = null) if (\is_array($mixed) === false) { throw new InvalidArgumentException(\sprintf( 'parseStrOpts expects string or array. %s provided.', - self::getTypeDebug($mixed) + self::getDebugType($mixed) )); } $mixed = \array_intersect_key($mixed, self::$parseStrOpts); diff --git a/src/HttpMessage/AbstractStream.php b/src/HttpMessage/AbstractStream.php index c0b2f3b5..94a05903 100644 --- a/src/HttpMessage/AbstractStream.php +++ b/src/HttpMessage/AbstractStream.php @@ -38,13 +38,13 @@ abstract class AbstractStream protected $resource; /** - * Return object class or value type + * Gets the type name of a variable in a way that is suitable for debugging * * @param mixed $value The value being type checked * * @return string */ - protected static function getTypeDebug($value) + protected static function getDebugType($value) { return \is_object($value) ? \get_class($value) @@ -107,7 +107,7 @@ protected function setResource($value) } throw new InvalidArgumentException(\sprintf( $this->strings['resourceInvalidType'], - $this->getTypeDebug($value) + $this->getDebugType($value) )); } diff --git a/src/HttpMessage/AssertionTrait.php b/src/HttpMessage/AssertionTrait.php index e2ccc4ee..c07720b9 100644 --- a/src/HttpMessage/AssertionTrait.php +++ b/src/HttpMessage/AssertionTrait.php @@ -42,18 +42,18 @@ protected function assertString($value, $what = '', $allowNumeric = false) throw new InvalidArgumentException(\sprintf( '%s must be a string, %s provided.', \ucfirst($what), - $this->getTypeDebug($value) + $this->getDebugType($value) )); } /** - * Get the value's type + * Gets the type name of a variable in a way that is suitable for debugging * * @param mixed $value Value to inspect * * @return string */ - protected static function getTypeDebug($value) + protected static function getDebugType($value) { return \is_object($value) ? \get_class($value) @@ -108,7 +108,7 @@ private function assertHeaderValue($value) if (\is_array($value) === false) { throw new InvalidArgumentException(\sprintf( 'The header field value only accepts string and array, %s provided.', - self::getTypeDebug($value) + self::getDebugType($value) )); } if (empty($value)) { @@ -169,7 +169,7 @@ private function assertProtocolVersion($version) if (\is_numeric($version) === false) { throw new InvalidArgumentException(\sprintf( 'Unsupported HTTP protocol version number. %s provided.', - self::getTypeDebug($version) + self::getDebugType($version) )); } if (\in_array((string) $version, $this->validProtocolVers, true) === false) { @@ -290,7 +290,7 @@ protected function assertParsedBody($data) } throw new InvalidArgumentException(\sprintf( 'ParsedBody must be array, object, or null. %s provided.', - self::getTypeDebug($data) + self::getDebugType($data) )); } @@ -309,7 +309,7 @@ protected function assertUploadedFiles($uploadedFiles) if (!($val instanceof UploadedFileInterface)) { throw new InvalidArgumentException(\sprintf( 'Invalid file in uploaded files structure. Expected UploadedFileInterface, %s provided', - self::getTypeDebug($val) + self::getDebugType($val) )); } }); @@ -362,7 +362,7 @@ protected function assertStatusCode($code) if (\is_int($code) === false) { throw new InvalidArgumentException(\sprintf( 'Status code must to be an integer, %s provided.', - self::getTypeDebug($code) + self::getDebugType($code) )); } if ($code < 100 || $code > 599) { @@ -421,7 +421,7 @@ protected function assertPort($port) if (\is_int($port) === false) { throw new InvalidArgumentException(\sprintf( 'Port must be a int, %s provided.', - $this->getTypeDebug($port) + $this->getDebugType($port) )); } if ($port < 1 || $port > 0xffff) { diff --git a/src/PubSub/Event.php b/src/PubSub/Event.php index 35450ae9..e5f3a4d6 100644 --- a/src/PubSub/Event.php +++ b/src/PubSub/Event.php @@ -6,8 +6,8 @@ * @package bdk\PubSub * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent - * @version v2.4 + * @copyright 2014-2023 Brad Kent + * @version v3.0 * @link http://www.github.com/bkdotcom/PubSub */ diff --git a/src/PubSub/Manager.php b/src/PubSub/Manager.php index be488748..5132ef79 100644 --- a/src/PubSub/Manager.php +++ b/src/PubSub/Manager.php @@ -6,13 +6,14 @@ * @package bdk\PubSub * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent - * @version v2.4 + * @copyright 2014-2023 Brad Kent + * @version v3.0 * @link http://www.github.com/bkdotcom/PubSub */ namespace bdk\PubSub; +use bdk\PubSub\ManagerHelperTrait; use bdk\PubSub\SubscriberInterface; /** @@ -20,11 +21,14 @@ */ class Manager { - const EVENT_PHP_SHUTDOWN = 'php.shutdown'; + use ManagerHelperTrait; + const DEFAULT_PRIORITY = 0; + const EVENT_PHP_SHUTDOWN = 'php.shutdown'; - private $subscribers = array(); - private $sorted = array(); + protected $subscribers = array(); + protected $sorted = array(); + protected $subscriberStack = array(); /** * Constructor @@ -46,15 +50,19 @@ public function __construct() * * @param SubscriberInterface $interface object implementing subscriber interface * - * @return array a normalized list of subscriptions added. + * @return array A normalized list of subscriptions added. */ public function addSubscriberInterface(SubscriberInterface $interface) { $subscribersByEvent = $this->getInterfaceSubscribers($interface); - foreach ($subscribersByEvent as $eventName => $subscribers) { - foreach ($subscribers as $methodPriority) { - $callable = array($interface, $methodPriority[0]); - $this->subscribe($eventName, $callable, $methodPriority[1]); + foreach ($subscribersByEvent as $eventName => $eventSubscribers) { + foreach ($eventSubscribers as $subscriberInfo) { + $this->subscribe( + $eventName, + $subscriberInfo['callable'], + $subscriberInfo['priority'], + $subscriberInfo['onlyOnce'] + ); } } return $subscribersByEvent; @@ -75,16 +83,12 @@ public function getSubscribers($eventName = null) if (!isset($this->subscribers[$eventName])) { return array(); } - if (!isset($this->sorted[$eventName])) { - $this->prepSubscribers($eventName); - } + $this->setSorted($eventName); return $this->sorted[$eventName]; } // return all subscribers foreach (\array_keys($this->subscribers) as $eventName) { - if (!isset($this->sorted[$eventName])) { - $this->prepSubscribers($eventName); - } + $this->setSorted($eventName); } return \array_filter($this->sorted); } @@ -140,10 +144,9 @@ public function publish($eventName, $eventOrSubject = null, array $values = arra public function removeSubscriberInterface(SubscriberInterface $interface) { $subscribersByEvent = $this->getInterfaceSubscribers($interface); - foreach ($subscribersByEvent as $eventName => $subscribers) { - foreach ($subscribers as $methodPriority) { - $callable = array($interface, $methodPriority[0]); - $this->unsubscribe($eventName, $callable); + foreach ($subscribersByEvent as $eventName => $eventSubscribers) { + foreach ($eventSubscribers as $subscriberInfo) { + $this->unsubscribe($eventName, $subscriberInfo['callable']); } } return $subscribersByEvent; @@ -166,14 +169,26 @@ public function removeSubscriberInterface(SubscriberInterface $interface) * @param string $eventName event name * @param callable|array $callable callable or closure factory * @param int $priority The higher this value, the earlier we handle event + * @param bool $onlyOnce (false) Auto-unsubscribe after first invocation * * @return void */ - public function subscribe($eventName, $callable, $priority = 0) + public function subscribe($eventName, $callable, $priority = 0, $onlyOnce = false) { unset($this->sorted[$eventName]); // clear the sorted cache $this->assertCallable($callable); - $this->subscribers[$eventName][$priority][] = $callable; + $subscriberInfo = array( + 'callable' => $callable, + 'onlyOnce' => $onlyOnce, + 'priority' => $priority, + ); + $this->subscribers[$eventName][$priority][] = $subscriberInfo; + // add to active event subscribers + foreach ($this->subscriberStack as $i => $stackInfo) { + if ($stackInfo['eventName'] === $eventName) { + $this->subscribeActive($i, $subscriberInfo); + } + } } /** @@ -186,173 +201,18 @@ public function subscribe($eventName, $callable, $priority = 0) */ public function unsubscribe($eventName, $callable) { - if (!isset($this->subscribers[$eventName])) { - return; - } if ($this->isClosureFactory($callable)) { $callable = $this->doClosureFactory($callable); } $this->prepSubscribers($eventName); - foreach ($this->subscribers[$eventName] as $priority => $subscribers) { - foreach ($subscribers as $k => $subscriber) { - if ($subscriber === $callable) { - unset($this->subscribers[$eventName][$priority][$k], $this->sorted[$eventName]); - } - } - if (empty($this->subscribers[$eventName][$priority])) { - unset($this->subscribers[$eventName][$priority]); - } - } - } - - /** - * Test if value is a callable or "closure factory" - * - * @param mixed $val Value to test - * - * @return void - * - * @throws \InvalidArgumentException - */ - private function assertCallable($val) - { - if (\is_callable($val, true)) { - return; - } - if ($this->isClosureFactory($val)) { - return; - } - throw new \InvalidArgumentException(\sprintf( - 'Expected callable or "closure factory", but %s provided', - \is_object($val) ? \get_class($val) : \gettype($val) - )); - } - - /** - * Instantiate the object wrapped in the closure factory - * closure factory may be - * [Closure, 'methodName'] - closure returns object - * [Closure] - closure returns object that is callable (ie has __invoke) - * - * @param array $closureFactory "closure factory" lazy loads an object / subscriber - * - * @return callable - */ - private function doClosureFactory($closureFactory = array()) - { - $closureFactory[0] = $closureFactory[0]($this); - return \count($closureFactory) === 1 - ? $closureFactory[0] // invokeable object - : $closureFactory; // [obj, 'method'] - } - - /** - * Calls the subscribers of an event. - * - * @param string $eventName The name of the event to publish - * @param callable[] $subscribers The event subscribers - * @param Event $event The event object to pass to the subscribers - * - * @return void - */ - protected function doPublish($eventName, $subscribers, Event $event) - { - foreach ($subscribers as $callable) { - if ($event->isPropagationStopped()) { - break; - } - \call_user_func($callable, $event, $eventName, $this); - } - } - - /** - * Does val appear to be a "closure factory"? - * array & array[0] instanceof Closure - * - * @param mixed $val value to check - * - * @return bool - * - * @psalm-assert-if-true array $val - */ - private function isClosureFactory($val) - { - return \is_array($val) && isset($val[0]) && $val[0] instanceof \Closure; - } - - /** - * Calls the passed object's getSubscriptions() method and returns a normalized list of subscriptions - * - * @param SubscriberInterface $interface object implementing subscriber interface - * - * @return array - */ - private function getInterfaceSubscribers(SubscriberInterface $interface) - { - $subscribers = array(); - foreach ($interface->getSubscriptions() as $eventName => $mixed) { - $subscribers[$eventName] = $this->normalizeInterfaceSubscribers($mixed); - } - return $subscribers; - } - - /** - * Normalize event subscribers - * - * @param string|array $mixed method(s) with priority - * - * @return array list of array(methodName, priority) - */ - private function normalizeInterfaceSubscribers($mixed) - { - if (\is_string($mixed)) { - // methodName - return array( - array($mixed, self::DEFAULT_PRIORITY), - ); - } - if (\count($mixed) === 2 && \is_int($mixed[1])) { - // ['methodName', priority] - return array( - $mixed, - ); - } - // array of methods - $eventSubscribers = array(); - foreach ($mixed as $mixed2) { - if (\is_string($mixed2)) { - // methodName - $eventSubscribers[] = array($mixed2, self::DEFAULT_PRIORITY); - continue; - } - // array(methodName[, priority]) - $priority = isset($mixed2[1]) - ? $mixed2[1] - : self::DEFAULT_PRIORITY; - $eventSubscribers[] = array($mixed2[0], $priority); - } - return $eventSubscribers; - } - - /** - * Sorts the internal list of subscribers for the given event by priority. - * Any closure factories for eventName are invoked - * - * @param string $eventName The name of the event - * - * @return void - */ - private function prepSubscribers($eventName) - { - \krsort($this->subscribers[$eventName]); - $this->sorted[$eventName] = array(); - foreach ($this->subscribers[$eventName] as $priority => $subscribers) { - foreach ($subscribers as $k => $subscriber) { - if ($this->isClosureFactory($subscriber)) { - $subscriber = $this->doClosureFactory($subscriber); - $this->subscribers[$eventName][$priority][$k] = $subscriber; - } - $this->sorted[$eventName][] = $subscriber; + $priorities = \array_keys($this->subscribers[$eventName]); + foreach ($priorities as $priority) { + $this->unsubscribeFromPriority($eventName, $callable, $priority, false); + } + // remove from any active events + foreach ($this->subscriberStack as $i => $stackInfo) { + if ($stackInfo['eventName'] === $eventName) { + $this->unsubscribeActive($i, $callable, $priority); } } } diff --git a/src/PubSub/ManagerHelperTrait.php b/src/PubSub/ManagerHelperTrait.php new file mode 100644 index 00000000..03b3d904 --- /dev/null +++ b/src/PubSub/ManagerHelperTrait.php @@ -0,0 +1,363 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2023 Brad Kent + * @version v3.0 + * @link http://www.github.com/bkdotcom/PubSub + */ + +namespace bdk\PubSub; + +use bdk\PubSub\SubscriberInterface; +use InvalidArgumentException; +use RuntimeException; + +/** + * Support methods for manager + */ +trait ManagerHelperTrait +{ + /** + * Test if value is a callable or "closure factory" + * + * @param mixed $val Value to test + * + * @return void + * + * @throws InvalidArgumentException + */ + private static function assertCallable($val) + { + if (\is_callable($val, true)) { + return; + } + if (self::isClosureFactory($val)) { + return; + } + throw new InvalidArgumentException(\sprintf( + 'Expected callable or "closure factory", but %s provided', + self::getDebugType($val) + )); + } + + /** + * Instantiate the object wrapped in the closure factory + * closure factory may be + * [Closure, 'methodName'] - closure returns object + * [Closure] - closure returns object that is callable (ie has __invoke) + * + * @param array $closureFactory "closure factory" lazy loads an object / subscriber + * + * @return callable + */ + private function doClosureFactory($closureFactory = array()) + { + $closureFactory[0] = $closureFactory[0]($this); + return \count($closureFactory) === 1 + ? $closureFactory[0] // invokeable object + : $closureFactory; // [obj, 'method'] + } + + /** + * Calls the subscribers of an event. + * + * @param string $eventName The name of the event to publish + * @param array $subscribers The event subscribers + * @param Event $event The event object to pass to the subscribers + * + * @return void + */ + protected function doPublish($eventName, $subscribers, Event $event) + { + $this->subscriberStack[] = array( + 'eventName' => $eventName, + 'subscribers' => $subscribers, + ); + $stackIndex = \count($this->subscriberStack) - 1; + $subscribers = &$this->subscriberStack[$stackIndex]['subscribers']; + while ($subscribers) { + if ($event->isPropagationStopped()) { + break; + } + $subscriberInfo = \array_shift($subscribers); + \call_user_func($subscriberInfo['callable'], $event, $eventName, $this); + if ($subscriberInfo['onlyOnce']) { + $this->unsubscribeFromPriority($eventName, $subscriberInfo['callable'], $subscriberInfo['priority'], true); + } + } + \array_pop($this->subscriberStack); + } + + /** + * Gets the type name of a variable in a way that is suitable for debugging + * + * @param mixed $value Value to inspect + * + * @return string + */ + private static function getDebugType($value) + { + return \is_object($value) + ? \get_class($value) + : \gettype($value); + } + + /** + * Calls the passed object's getSubscriptions() method and returns a normalized list of subscriptions + * + * @param SubscriberInterface $interface SubscriberInterface instance + * + * @return array + * + * @throws RuntimeException + */ + private static function getInterfaceSubscribers(SubscriberInterface $interface) + { + $subscriptions = $interface->getSubscriptions(); + if (\is_array($subscriptions) === false) { + throw new RuntimeException(\sprintf( + 'Expected array from %s::getSubscriptions(). Got %s', + \get_class($interface), + self::getDebugType($subscriptions) + )); + } + foreach ($subscriptions as $eventName => $mixed) { + $eventSubscribers = self::normalizeInterfaceSubscribers($interface, $mixed); + if ($eventSubscribers === false) { + throw new RuntimeException(\sprintf( + '%s::getSubscriptions(): Unexpected subscriber(s) defined for %s', + \get_class($interface), + $eventName + )); + } + $subscriptions[$eventName] = $eventSubscribers; + } + return $subscriptions; + } + + /** + * Does val appear to be a "closure factory"? + * array & array[0] instanceof Closure + * + * @param mixed $val value to check + * + * @return bool + * + * @psalm-assert-if-true array $val + */ + private static function isClosureFactory($val) + { + return \is_array($val) && isset($val[0]) && $val[0] instanceof \Closure; + } + + /** + * Normalize event subscribers + * + * @param SubscriberInterface $interface SubscriberInterface instance + * @param string|array $mixed method(s) with optional priority/onlyOnce + * + * @return array|false list of array(methodName, priority) + */ + private static function normalizeInterfaceSubscribers(SubscriberInterface $interface, $mixed) + { + // test if single subscriber + $subscriberInfo = self::normalizeInterfaceSubscriber($mixed); + if ($subscriberInfo) { + $subscriberInfo['callable'] = array($interface, $subscriberInfo['callable']); + return array($subscriberInfo); + } + if (\is_array($mixed) === false) { + return false; + } + // multiple subscribers + $eventSubscribers = array(); + foreach ($mixed as $mixed2) { + $subscriberInfo = self::normalizeInterfaceSubscriber($mixed2); + if ($subscriberInfo) { + $subscriberInfo['callable'] = array($interface, $subscriberInfo['callable']); + $eventSubscribers[] = $subscriberInfo; + continue; + } + return false; + } + return $eventSubscribers; + } + + /** + * Test if value defines method/priority/onlyOnce + * + * @param string|array $mixed method/priority/onlyOnce info + * + * @return array|false + */ + private static function normalizeInterfaceSubscriber($mixed) + { + $subscriberInfo = array( + 'callable' => null, + 'onlyOnce' => false, + 'priority' => self::DEFAULT_PRIORITY, + ); + if (\is_string($mixed)) { + $subscriberInfo['callable'] = $mixed; + return $subscriberInfo; + } + if (\is_array($mixed)) { + $subscriberInfo = self::normalizeInterfaceSubscriberArray($mixed, $subscriberInfo); + } + return $subscriberInfo['callable'] !== null + ? $subscriberInfo + : false; + } + + /** + * Test if given array defines method/priority/onlyOnce + * + * @param array $values array values + * @param array $subscriberInfo default subscriberInfo values + * + * @return array updated subscriberInfo + */ + private static function normalizeInterfaceSubscriberArray(array $values, array $subscriberInfo) + { + $tests = array( + 'callable' => 'is_string', + 'onlyOnce' => 'is_bool', + 'priority' => 'is_int', + ); + while ($values && $tests) { + $val = \array_shift($values); + foreach ($tests as $key => $test) { + if ($test($val)) { + $subscriberInfo[$key] = $val; + unset($tests[$key]); + continue 2; + } + } + // all tests failed for current value + $subscriberInfo['callable'] = null; + break; + } + return $subscriberInfo; + } + + /** + * Sorts the internal list of subscribers for the given event by priority. + * Any closure factories for eventName are invoked + * + * @param string $eventName The name of the event + * + * @return void + */ + private function prepSubscribers($eventName) + { + if (!isset($this->subscribers[$eventName])) { + $this->subscribers[$eventName] = array(); + } + \krsort($this->subscribers[$eventName]); + $this->sorted[$eventName] = array(); + $priorities = \array_keys($this->subscribers[$eventName]); + \array_map(function ($priority) use ($eventName) { + $eventSubscribers = $this->subscribers[$eventName][$priority]; + foreach ($eventSubscribers as $k => $subscriberInfo) { + if ($this->isClosureFactory($subscriberInfo['callable'])) { + $subscriberInfo['callable'] = $this->doClosureFactory($subscriberInfo['callable']); + $this->subscribers[$eventName][$priority][$k] = $subscriberInfo; + } + $this->sorted[$eventName][] = $subscriberInfo; + } + }, $priorities); + } + + /** + * Prep and sort subscribers for the specified event name + * + * @param string $eventName The name of the event + * + * @return void + */ + private function setSorted($eventName) + { + if (!isset($this->sorted[$eventName])) { + $this->prepSubscribers($eventName); + } + } + + /** + * Add subscriber to subscriber stack + * + * @param int $stackIndex subscriberStack index to add to + * @param array $subscriberInfoNew subscriber info (callable, priority, onlyOnce) + * + * @return void + */ + private function subscribeActive($stackIndex, array $subscriberInfoNew) + { + $eventSubscribers = $this->subscriberStack[$stackIndex]['subscribers']; + $priority = $subscriberInfoNew['priority']; + foreach ($eventSubscribers as $i => $subscriberInfo) { + if ($priority > $subscriberInfo['priority']) { + \array_splice($this->subscriberStack[$stackIndex]['subscribers'], $i, 0, array($subscriberInfoNew)); + return; + } + } + $this->subscriberStack[$stackIndex]['subscribers'][] = $subscriberInfoNew; + } + + /** + * Remove callable from active event subscribers + * + * @param int $stackIndex subscriberStack index to add to + * @param callable $callable callable + * @param int $priority The priority + * + * @return void + */ + private function unsubscribeActive($stackIndex, $callable, $priority) + { + $search = \array_filter(array( + 'callable' => $callable, + 'priority' => $priority, + )); + $eventSubscribers = $this->subscriberStack[$stackIndex]['subscribers']; + foreach ($eventSubscribers as $i => $subscriberInfo) { + if (\array_intersect_key($subscriberInfo, $search) === $search) { + \array_splice($this->subscriberStack[$stackIndex]['subscribers'], $i, 1); + } + } + } + + /** + * Find callable in eventName/priority array and remove it + * + * @param string $eventName The event we're unsubscribing from + * @param callable $callable callable + * @param int $priority The priority + * @param bool $onlyOnce Only unsubscribe "onlyOnce" subscribers + * + * @return void + */ + private function unsubscribeFromPriority($eventName, $callable, $priority, $onlyOnce) + { + $search = \array_filter(array( + 'callable' => $callable, + 'onlyOnce' => $onlyOnce, + )); + foreach ($this->subscribers[$eventName][$priority] as $k => $subscriberInfo) { + if (\array_intersect_key($subscriberInfo, $search) !== $search) { + continue; + } + unset($this->subscribers[$eventName][$priority][$k], $this->sorted[$eventName]); + if ($onlyOnce) { + break; + } + } + if (empty($this->subscribers[$eventName][$priority])) { + unset($this->subscribers[$eventName][$priority]); + } + } +} diff --git a/src/PubSub/ValueStore.php b/src/PubSub/ValueStore.php index 93d58601..9642ceeb 100644 --- a/src/PubSub/ValueStore.php +++ b/src/PubSub/ValueStore.php @@ -7,7 +7,7 @@ * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT * @copyright 2014-2023 Brad Kent - * @version v2.4 + * @version v3.0 * @link http://www.github.com/bkdotcom/PubSub */ @@ -215,6 +215,7 @@ public function offsetSet($key, $value) if ($key === null) { // appending... determine key $this->values[] = $value; + \end($this->values); $key = \key($this->values); } $this->values[$key] = $value; diff --git a/src/Teams/CardUtilityTrait.php b/src/Teams/CardUtilityTrait.php index 251f3490..209984f9 100644 --- a/src/Teams/CardUtilityTrait.php +++ b/src/Teams/CardUtilityTrait.php @@ -63,7 +63,7 @@ protected static function assertBool($val, $name) throw new InvalidArgumentException(\sprintf( '%s must be bool. %s provided', $name, - self::getTypeDebug($val) + self::getDebugType($val) )); } @@ -152,7 +152,7 @@ protected static function assertPx($val, $method = null, $paramName = null) $message = $paramName ? $message . ' supplied for ' . $paramName . '.' : $message . '.'; - $message .= ' ' . self::getTypeDebug($val) . ' provided.'; + $message .= ' ' . self::getDebugType($val) . ' provided.'; throw new InvalidArgumentException($message); } @@ -188,7 +188,7 @@ protected static function asString($val, $allowNull, $method, $paramName = null) throw new InvalidArgumentException(\sprintf( $message, $method, - self::getTypeDebug($val) + self::getDebugType($val) )); } @@ -207,7 +207,7 @@ protected static function assertUrl($val, $allowDataUrl = false) if (\is_string($val) === false) { throw new InvalidArgumentException(\sprintf( 'Url should be a string. %s provided.', - \gettype($val) + self::getDebugType($val) )); } if ( @@ -227,13 +227,13 @@ protected static function assertUrl($val, $allowDataUrl = false) } /** - * Get the value's type + * Gets the type name of a variable in a way that is suitable for debugging * * @param mixed $value Value to inspect * * @return string */ - protected static function getTypeDebug($value) + protected static function getDebugType($value) { return \is_object($value) ? \get_class($value) diff --git a/src/Teams/Elements/FactSet.php b/src/Teams/Elements/FactSet.php index 6e68e3c4..3b707b4a 100644 --- a/src/Teams/Elements/FactSet.php +++ b/src/Teams/Elements/FactSet.php @@ -93,7 +93,7 @@ private function asFacts(array $facts) throw new InvalidArgumentException(\sprintf( 'Invalid Fact or value encountered at %s. Expected Fact, string, or numeric. %s provided.', $key, - self::getTypeDebug($value) + self::getDebugType($value) )); }); return $factsNew; diff --git a/src/Teams/Elements/RichTextBlock.php b/src/Teams/Elements/RichTextBlock.php index 00033344..47f27945 100644 --- a/src/Teams/Elements/RichTextBlock.php +++ b/src/Teams/Elements/RichTextBlock.php @@ -150,7 +150,7 @@ private static function assertInline($val) } throw new InvalidArgumentException(\sprintf( 'Inline must be TextRun or string. %s provided.', - \gettype($val) + \is_object($val) ? \get_class($val) : \gettype($val) )); } } diff --git a/src/Teams/Elements/Table.php b/src/Teams/Elements/Table.php index a3e9ec0c..dbb3b531 100644 --- a/src/Teams/Elements/Table.php +++ b/src/Teams/Elements/Table.php @@ -264,7 +264,7 @@ private function asRows($rows) if ($isIterable === false) { throw new InvalidArgumentException(\sprintf( 'Invalid rows. Expecting iterator of rows. %s provided.', - self::getTypeDebug($rows) + self::getDebugType($rows) )); } foreach ($rows as $i => $row) { diff --git a/src/Teams/Elements/TableCell.php b/src/Teams/Elements/TableCell.php index dcdfa3a5..bc9eb015 100644 --- a/src/Teams/Elements/TableCell.php +++ b/src/Teams/Elements/TableCell.php @@ -270,7 +270,7 @@ private function asItem($item) } throw new InvalidArgumentException(\sprintf( 'Invalid TableCell item found. Expecting ElementInterface, stringable, scalar, or null. %s provided.', - self::getTypeDebug($item) + self::getDebugType($item) )); } @@ -292,7 +292,7 @@ private function asItems(array $items) } catch (InvalidArgumentException $e) { throw new InvalidArgumentException(\sprintf( 'Invalid TableCell item type (%s) found at index %s', - self::getTypeDebug($item), + self::getDebugType($item), $i )); } diff --git a/src/Teams/Elements/TableRow.php b/src/Teams/Elements/TableRow.php index d5b143cc..884d52fd 100644 --- a/src/Teams/Elements/TableRow.php +++ b/src/Teams/Elements/TableRow.php @@ -168,7 +168,7 @@ private function asCells($cells) throw new InvalidArgumentException(\sprintf( 'Invalid table cell found at index %s. Expected TableCell, ElementInterface, stringable, scalar, or null. %s provided.', $i, - self::getTypeDebug($cell) + self::getDebugType($cell) )); } return \array_values($cells); diff --git a/src/Teams/Elements/TextBlock.php b/src/Teams/Elements/TextBlock.php index a2a0e4e3..3edcbcb1 100644 --- a/src/Teams/Elements/TextBlock.php +++ b/src/Teams/Elements/TextBlock.php @@ -160,7 +160,7 @@ public function withMaxLines($maxLines) if ($isValid === false) { throw new InvalidArgumentException(\sprintf( 'withMaxLines expects int or null. %s provided.', - self::getTypeDebug($maxLines) + self::getDebugType($maxLines) )); } if ($maxLines < 1) { diff --git a/tests/CurlHttpMessage/Handler/MockTest.php b/tests/CurlHttpMessage/Handler/MockTest.php index f38923ac..bbbbea47 100644 --- a/tests/CurlHttpMessage/Handler/MockTest.php +++ b/tests/CurlHttpMessage/Handler/MockTest.php @@ -1,6 +1,6 @@ debug->setCfg($name, $closure2); $closure1isSub = false; $closure2isSub = false; - foreach ($this->debug->eventManager->getSubscribers($event) as $sub) { - if ($sub === $closure1) { + foreach ($this->debug->eventManager->getSubscribers($event) as $subInfo) { + if ($subInfo['callable'] === $closure1) { $closure1isSub = true; } - if ($sub === $closure2) { + if ($subInfo['callable'] === $closure2) { $closure2isSub = true; } } diff --git a/tests/Debug/DebugTest.php b/tests/Debug/DebugTest.php index 30debdc6..6baafdd0 100644 --- a/tests/Debug/DebugTest.php +++ b/tests/Debug/DebugTest.php @@ -199,9 +199,9 @@ public function testInitViaStatic() Lets clear all of its subscribers */ $eventManager = Debug::getInstance()->eventManager; - foreach ($eventManager->getSubscribers() as $eventName => $subs) { - foreach ($subs as $sub) { - $eventManager->unsubscribe($eventName, $sub); + foreach ($eventManager->getSubscribers() as $eventName => $eventSubscribers) { + foreach ($eventSubscribers as $subInfo) { + $eventManager->unsubscribe($eventName, $subInfo['callable']); } } @@ -220,15 +220,16 @@ public function testInitViaStatic() */ public function testShutDownSubscribers() { - $subscribers = \array_map(function ($val) { - if (\is_array($val)) { - return array(\get_class($val[0]), $val[1]); + $subscribers = \array_map(function ($subInfo) { + $callable = $subInfo['callable']; + if (\is_array($callable)) { + return array(\get_class($callable[0]), $callable[1]); } - if ($val instanceof \Closure) { - $abs = $this->debug->abstracter->crate($val); + if ($callable instanceof \Closure) { + $abs = $this->debug->abstracter->crate($callable); return 'Closure(' . $abs['properties']['debug.file']['value'] . ')'; } - return \gettype($val); + return \gettype($callable); }, $this->debug->eventManager->getSubscribers(EventManager::EVENT_PHP_SHUTDOWN)); $subscribersExpect = array( array('bdk\Debug\InternalEvents', 'onShutdownHigh'), diff --git a/tests/Debug/DebugTestFramework.php b/tests/Debug/DebugTestFramework.php index a58e420f..e9401824 100644 --- a/tests/Debug/DebugTestFramework.php +++ b/tests/Debug/DebugTestFramework.php @@ -145,20 +145,22 @@ public function tearDown(): void { $this->debug->setCfg('output', false); $subscribers = $this->debug->eventManager->getSubscribers(Debug::EVENT_OUTPUT); - foreach ($subscribers as $subscriber) { + foreach ($subscribers as $subscriberInfo) { $unsub = false; - if ($subscriber instanceof \Closure) { + $callable = $subscriberInfo['callable']; + if ($callable instanceof \Closure) { $unsub = true; - } elseif (\is_array($subscriber) && \strpos(\get_class($subscriber[0]), 'bdk\\Debug') === false) { + } elseif (\is_array($callable) && \strpos(\get_class($callable[0]), 'bdk\\Debug') === false) { $unsub = true; } if ($unsub) { - $this->debug->eventManager->unsubscribe(Debug::EVENT_OUTPUT, $subscriber); + $this->debug->eventManager->unsubscribe(Debug::EVENT_OUTPUT, $callable); } } $subscribers = $this->debug->eventManager->getSubscribers(Debug::EVENT_OUTPUT_LOG_ENTRY); - foreach ($subscribers as $subscriber) { - $this->debug->eventManager->unsubscribe(Debug::EVENT_OUTPUT_LOG_ENTRY, $subscriber); + foreach ($subscribers as $subscriberInfo) { + $callable = $subscriberInfo['callable']; + $this->debug->eventManager->unsubscribe(Debug::EVENT_OUTPUT_LOG_ENTRY, $callable); } } diff --git a/tests/Debug/Method/PluginMethodReqResTest.php b/tests/Debug/Method/PluginMethodReqResTest.php index 5dc2ef62..07b374f1 100644 --- a/tests/Debug/Method/PluginMethodReqResTest.php +++ b/tests/Debug/Method/PluginMethodReqResTest.php @@ -177,7 +177,7 @@ public function testWriteToHttpFoundationResponse() public function testWriteToResponseInvalid() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('writeToResponse expects ResponseInterface or HttpFoundationResponse, but NULL provided'); + $this->expectExceptionMessage('writeToResponse expects ResponseInterface or HttpFoundationResponse, but null provided'); $this->debug->writeToResponse(null); } } diff --git a/tests/Debug/Method/TraceTest.php b/tests/Debug/Method/TraceTest.php index 0290ef5d..a89f3d60 100644 --- a/tests/Debug/Method/TraceTest.php +++ b/tests/Debug/Method/TraceTest.php @@ -162,7 +162,7 @@ public function testTraceInvalidCaption() $logEntryTrace = $this->debug->data->get('log/1'); $this->assertSame(array( 'method' => 'warn', - 'args' => array('trace caption should be a string. boolean provided'), + 'args' => array('trace caption should be a string. bool provided'), 'meta' => array( 'detectFiles' => true, 'file' => __FILE__, diff --git a/tests/Debug/Utility/PhpTest.php b/tests/Debug/Utility/PhpTest.php index c773bd58..79c69c9f 100644 --- a/tests/Debug/Utility/PhpTest.php +++ b/tests/Debug/Utility/PhpTest.php @@ -29,6 +29,48 @@ public function testFriendlyClassName($input, $expect) self::assertSame($expect, Php::friendlyClassName($input)); } + public static function providerGetDebugType() + { + $callbackFunc = \ini_set('unserialize_callback_func', null); + $incompleteClass = \unserialize('O:8:"Foo\Buzz":0:{}'); + \ini_set('unserialize_callback_func', $callbackFunc); + + $fh = \fopen(__FILE__, 'r'); + \fclose($fh); + + // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder + $tests = array( + 'object' => array(new \stdClass(), 'stdClass'), + 'string' => array('foo', 'string'), + 'false' => array(false, 'bool'), + 'true' => array(true, 'bool'), + 'null' => array(null, 'null'), + 'array' => array(array(), 'array'), + 'int' => array(42, 'int'), + 'float' => array(3.14, 'float'), + 'stream' => array(\fopen(__FILE__, 'r'), 'resource (stream)'), + 'closed resource' => array($fh, 'resource (closed)'), + '__PHP_Incomplete_Class' => array($incompleteClass, '__PHP_Incomplete_Class'), + ); + if (PHP_VERSION_ID >= 70000) { + $tests = \array_merge($tests, array( + 'anon' => array(eval('return new class() {};'), 'class@anonymous'), + 'anonExtends' => array(eval('return new class() extends stdClass {};'), 'stdClass@anonymous'), + 'anonImplements' => array(eval('return new class() implements Reflector { function __toString() {} public static function export() {} };'), 'Reflector@anonymous'), + )); + } + return $tests; + } + + /** + * @dataProvider providerGetDebugType + */ + public function testGetDebugType($val, $expectedType) + { + $type = Php::getDebugType($val); + self::assertSame($expectedType, $type); + } + public function testGetIncludedFiles() { $filesA = \get_included_files(); @@ -71,6 +113,7 @@ public function testMemoryLimit() public function testUnserializeSafe() { + // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder $serialized = \serialize(array( 'before' => 'foo', 'stdClass' => (object) array('foo' => 'bar'), @@ -79,6 +122,7 @@ public function testUnserializeSafe() )); // allow everything + // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder self::assertEquals(array( 'before' => 'foo', 'stdClass' => (object) array('foo' => 'bar'), @@ -88,6 +132,7 @@ public function testUnserializeSafe() // disable all (stdClass still allowed) $serialized = 'a:5:{s:6:"before";s:3:"foo";s:8:"stdClass";O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}s:12:"serializable";C:35:"bdk\Test\Debug\Fixture\Serializable":13:{Brad was here}s:3:"obj";O:38:"bdk\Test\Debug\Fixture\TestTraversable":1:{s:4:"data";a:1:{s:3:"foo";s:3:"bar";}}s:5:"after";s:3:"bar";}'; + // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder self::assertEquals(array( 'before' => 'foo', 'stdClass' => (object) array('foo' => 'bar'), @@ -97,11 +142,13 @@ public function testUnserializeSafe() ), Php::unserializeSafe($serialized, false)); // no Serializable (vanila unserialize will be used + // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder $serialized = \serialize(array( 'before' => 'foo', 'stdClass' => (object) array('foo' => 'bar'), 'after' => 'bar', )); + // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder self::assertEquals(array( 'before' => 'foo', 'stdClass' => (object) array('foo' => 'bar'), @@ -144,6 +191,7 @@ public static function providerIsCallable() echo $foo; }; $invokable = new \bdk\Test\Container\Fixture\Invokable(); + // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder $return = array( // closure 'closure' => array($closure, null, true), diff --git a/tests/Debug/Utility/StringUtilTest.php b/tests/Debug/Utility/StringUtilTest.php index 5e340891..58f8d07a 100644 --- a/tests/Debug/Utility/StringUtilTest.php +++ b/tests/Debug/Utility/StringUtilTest.php @@ -71,14 +71,14 @@ public function testInterpolate() public function testInterpolateInvalidMessage() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('bdk\Debug\Utility::interpolate()\'s $message expects string or Stringable object. boolean provided.'); + $this->expectExceptionMessage('bdk\Debug\Utility::interpolate()\'s $message expects string or Stringable object. bool provided.'); StringUtil::interpolate(false, 'string'); } public function testInterpolateInvalidContext() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('bdk\Debug\Utility::interpolate()\'s $context expects array or object for $context. string provided.'); + $this->expectExceptionMessage('bdk\Debug\Utility::interpolate()\'s $context expects array or object. string provided.'); StringUtil::interpolate('message', 'string'); }